diff options
512 files changed, 5668 insertions, 2729 deletions
@@ -5,12 +5,16 @@ gemspec if ENV['AREL'] gem "arel", :path => ENV['AREL'] else - gem "arel", '~> 2.1.0' + gem "arel", '~> 2.1.3' end +gem "jquery-rails" gem "coffee-script" gem "sass" -gem "uglifier", :git => "git://github.com/lautis/uglifier.git" + +# This needs to be with require false to avoid +# it being automatically loaded by sprockets +gem "uglifier", ">= 1.0.0", :require => false gem "rake", ">= 0.8.7" gem "mocha", ">= 0.9.8" @@ -19,6 +23,7 @@ group :doc do gem "rdoc", "~> 3.4" gem "horo", "= 1.0.3" gem "RedCloth", "~> 4.2" if RUBY_VERSION < "1.9.3" + gem "w3c_validators" end # AS @@ -41,10 +46,11 @@ 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" + gem "ruby-prof" if RUBY_VERSION < "1.9.3" + end # AR gem "sqlite3", "~> 1.3.3" @@ -52,7 +58,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/RAILS_VERSION b/RAILS_VERSION index 90040cdc46..dd8b7cd23b 100644 --- a/RAILS_VERSION +++ b/RAILS_VERSION @@ -1 +1 @@ -3.1.0.rc1 +3.2.0.beta diff --git a/README.rdoc b/README.rdoc index 5abc6f927c..bca2126559 100644 --- a/README.rdoc +++ b/README.rdoc @@ -1,7 +1,7 @@ == Welcome to Rails Rails is a web-application framework that includes everything needed to create -database-backed web applications according to the Model-View-Controller pattern. +database-backed web applications according to the {Model-View-Controller (MVC)}[http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller] pattern. Understanding the MVC pattern is key to understanding Rails. MVC divides your application into three layers, each with a specific responsibility. @@ -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 @@ -51,9 +51,31 @@ desc "Generate documentation for the Rails framework" RDoc::Task.new do |rdoc| RDOC_MAIN = 'RDOC_MAIN.rdoc' + # This is a hack. + # + # Backslashes are needed to prevent RDoc from autolinking "Rails" to the + # documentation of the Rails module. On the other hand, as of this + # writing README.rdoc is displayed in the front page of the project in + # GitHub, where backslashes are shown and look weird. + # + # 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. rdoc.before_running_rdoc do rdoc_main = File.read('README.rdoc') - rdoc_main.gsub!(/\b(?=Rails)\b/) { '\\' } + + # The ^(?=\S) assertion prevents code blocks from being processed, + # 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) end @@ -72,7 +94,7 @@ RDoc::Task.new do |rdoc| rdoc.rdoc_files.include('railties/MIT-LICENSE') rdoc.rdoc_files.include('railties/README.rdoc') rdoc.rdoc_files.include('railties/lib/**/*.rb') - rdoc.rdoc_files.exclude('railties/lib/rails/generators/**/templates/*') + rdoc.rdoc_files.exclude('railties/lib/rails/generators/**/templates/**/*.rb') rdoc.rdoc_files.include('activerecord/README.rdoc') rdoc.rdoc_files.include('activerecord/CHANGELOG') diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 3afdbea42a..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 @@ -213,14 +214,14 @@ module ActionMailer #:nodoc: # = Observing and Intercepting Mails # # Action Mailer provides hooks into the Mail observer and interceptor methods. These allow you to - # register objects that are called during the mail delivery life cycle. + # register classes that are called during the mail delivery life cycle. # - # An observer object must implement the <tt>:delivered_email(message)</tt> method which will be + # An observer class must implement the <tt>:delivered_email(message)</tt> method which will be # called once for every email sent after the email has been sent. # - # An interceptor object must implement the <tt>:delivering_email(message)</tt> method which will be + # An interceptor class must implement the <tt>:delivering_email(message)</tt> method which will be # called before the email is sent, allowing you to make modifications to the email before it hits - # the delivery agents. Your object should make any needed modifications directly to the passed + # the delivery agents. Your class should make any needed modifications directly to the passed # in Mail::Message instance. # # = Default Hash @@ -365,7 +366,7 @@ module ActionMailer #:nodoc: Mail.register_observer(delivery_observer) end - # Register an Inteceptor which will be called before mail is sent. + # Register an Interceptor which will be called before mail is sent. # Either a class or a string can be passed in as the Interceptor. If a string is passed in # it will be <tt>constantize</tt>d. def register_interceptor(interceptor) @@ -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/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb index 5654e54d7a..b74ba3cb0d 100644 --- a/actionmailer/lib/action_mailer/version.rb +++ b/actionmailer/lib/action_mailer/version.rb @@ -1,9 +1,9 @@ module ActionMailer module VERSION #:nodoc: MAJOR = 3 - MINOR = 1 + MINOR = 2 TINY = 0 - PRE = "rc1" + PRE = "beta" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 479e8246c5..b0f7d0bc11 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,14 @@ *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 + nothing is sent according to HTML spec [Bogdan Gusiev] + * Refactor ActionController::TestCase cookies [Andrew White] Assigning cookies for test cases should now use cookies[], e.g: @@ -21,7 +30,40 @@ *Rails 3.1.0 (unreleased)* -* Added 'ActionView::Helpers::FormHelper.fields_for_with_index', similar to fields_for but allows to have access to the current iteration index [Jorge Bejar] +* Make sure respond_with with :js tries to render a template in all cases [José Valim] + +* json_escape will now return a SafeBuffer string if it receives SafeBuffer string [tenderlove] + +* Make sure escape_js returns SafeBuffer string if it receives SafeBuffer string [Prem Sichanugrist] + +* Fix escape_js to work correctly with the new SafeBuffer restriction [Paul Gallagher] + +* Brought back alternative convention for namespaced models in i18n [thoefer] + + Now the key can be either "namespace.model" or "namespace/model" until further deprecation. + +* It is prohibited to perform a in-place SafeBuffer mutation [tenderlove] + + The old behavior of SafeBuffer allowed you to mutate string in place via + method like `sub!`. These methods can add unsafe strings to a safe buffer, + and the safe buffer will continue to be marked as safe. + + An example problem would be something like this: + + <%= link_to('hello world', @user).sub!(/hello/, params[:xss]) %> + + In the above example, an untrusted string (`params[:xss]`) is added to the + safe buffer returned by `link_to`, and the untrusted content is successfully + sent to the client without being escaped. To prevent this from happening + `sub!` and other similar methods will now raise an exception when they are called on a safe buffer. + + In addition to the in-place versions, some of the versions of these methods which return a copy of the string will incorrectly mark strings as safe. For example: + + <%= link_to('hello world', @user).sub(/hello/, params[:xss]) %> + + The new versions will now ensure that *all* strings returned by these methods on safe buffers are marked unsafe. + + You can read more about this change in http://groups.google.com/group/rubyonrails-security/browse_thread/thread/2e516e7acc96c4fb * Warn if we cannot verify CSRF token authenticity [José Valim] diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index ba793ee7b6..88d892eb16 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -18,13 +18,13 @@ Gem::Specification.new do |s| s.add_dependency('activesupport', version) s.add_dependency('activemodel', version) - s.add_dependency('rack-cache', '~> 1.0.1') + s.add_dependency('rack-cache', '~> 1.0.2') s.add_dependency('builder', '~> 3.0.0') s.add_dependency('i18n', '~> 0.6') - s.add_dependency('rack', '~> 1.3.0') + s.add_dependency('rack', '~> 1.3.1') s.add_dependency('rack-test', '~> 0.6.0') s.add_dependency('rack-mount', '~> 0.8.1') - s.add_dependency('sprockets', '~> 2.0.0.beta.9') - s.add_dependency('tzinfo', '~> 0.3.27') + s.add_dependency('sprockets', '= 2.0.0.beta.10') + s.add_dependency('tzinfo', '~> 0.3.29') 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.rb b/actionpack/lib/action_controller.rb index f13fd71050..f4eaa2fd1b 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -47,21 +47,6 @@ module ActionController eager_autoload do autoload :RecordIdentifier - - # TODO: Don't autoload exceptions, setup explicit - # requires for files that need them - autoload_at "action_controller/metal/exceptions" do - autoload :ActionControllerError - autoload :RenderError - autoload :RoutingError - autoload :MethodNotAllowed - autoload :NotImplemented - autoload :UnknownController - autoload :MissingFile - autoload :RenderError - autoload :SessionOverflowError - autoload :UnknownHttpMethod - end end end 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/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb index 0be04b70a1..abeb49d16f 100644 --- a/actionpack/lib/action_controller/caching/fragments.rb +++ b/actionpack/lib/action_controller/caching/fragments.rb @@ -12,13 +12,13 @@ module ActionController #:nodoc: # # <% cache do %> # All the topics in the system: - # <%= render :partial => "topic", :collection => Topic.find(:all) %> + # <%= render :partial => "topic", :collection => Topic.all %> # <% end %> # # This cache will bind the name of the action that called it, so if # this code was part of the view for the topics/list action, you # would be able to invalidate it using: - # + # # expire_fragment(:controller => "topics", :action => "list") # # This default behavior is limited if you need to cache multiple @@ -109,7 +109,6 @@ module ActionController #:nodoc: def expire_fragment(key, options = nil) return unless cache_configured? key = fragment_cache_key(key) unless key.is_a?(Regexp) - message = nil instrument_fragment_cache :expire_fragment, key do if key.is_a?(Regexp) diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb index 8c583c7ce0..496390402b 100644 --- a/actionpack/lib/action_controller/caching/pages.rb +++ b/actionpack/lib/action_controller/caching/pages.rb @@ -16,9 +16,10 @@ module ActionController #:nodoc: # caches_page :show, :new # end # - # This will generate cache files such as <tt>weblog/show/5.html</tt> and <tt>weblog/new.html</tt>, - # which match the URLs used to trigger the dynamic generation. This is how the web server is able - # pick up a cache file when it exists and otherwise let the request pass on to Action Pack to generate it. + # This will generate cache files such as <tt>weblog/show/5.html</tt> and <tt>weblog/new.html</tt>, which match the URLs used + # that would normally trigger dynamic page generation. Page caching works by configuring a web server to first check for the + # existence of files on disk, and to serve them directly when found, without passing the request through to Action Pack. + # This is much faster than handling the full dynamic request in the usual way. # # Expiration of the cache is handled by deleting the cached file, which results in a lazy regeneration approach where the cache # is not restored before another hit is made against it. The API for doing so mimics the options from +url_for+ and friends: @@ -132,8 +133,8 @@ module ActionController #:nodoc: end end - # Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used - # If no options are provided, the requested url is used. Example: + # Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used. + # If no options are provided, the url of the current request being handled is used. Example: # cache_page "I'm the cached content", :controller => "lists", :action => "show" def cache_page(content = nil, options = nil) return unless self.class.perform_caching && caching_allowed? 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..0785fe9679 100644 --- a/actionpack/lib/action_controller/metal/data_streaming.rb +++ b/actionpack/lib/action_controller/metal/data_streaming.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/file/path' +require 'action_controller/metal/exceptions' module ActionController #:nodoc: # Methods for sending arbitrary data and for streaming files to the browser, @@ -26,8 +27,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 +88,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 +114,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 +131,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/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index 75757db564..2df0e9422c 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -29,7 +29,7 @@ module ActionController # class EventsController < ActionController::Base # helper FormattedTimeHelper # def index - # @events = Event.find(:all) + # @events = Event.all # end # end # diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 2ad571b2b5..7420a5e7e9 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -106,7 +106,7 @@ module ActionController module ControllerMethods extend ActiveSupport::Concern - + module ClassMethods def http_basic_authenticate_with(options = {}) before_filter(options.except(:name, :password, :realm)) do @@ -116,7 +116,7 @@ module ActionController end end end - + def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure) authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm) end @@ -423,7 +423,7 @@ module ActionController # Returns an Array of [String, Hash] if a token is present. # Returns nil if no token is found. def token_and_options(request) - if header = request.authorization.to_s[/^Token (.*)/] + if request.authorization.to_s[/^Token (.*)/] values = Hash[$1.split(',').map do |value| value.strip! # remove any spaces between commas and values key, value = value.split(/\=\"?/) # split key=value pairs diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index 5500f88930..2d8afc3a78 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -64,7 +64,7 @@ module ActionController # end # # will try to check if +Admin::User+ or +User+ model exists, and use it to - # determine the wrapper key respectively. If both of the model doesn't exists, + # determine the wrapper key respectively. If both models don't exist, # it will then fallback to use +user+ as the key. module ParamsWrapper extend ActiveSupport::Concern @@ -88,14 +88,14 @@ module ActionController # # wraps parameters into +params[:person]+ hash # # wrap_parameters Person - # # wraps parameters by determine the wrapper key from Person class + # # wraps parameters by determining the wrapper key from Person class # (+person+, in this case) and the list of attribute names # # wrap_parameters :include => [:username, :title] # # wraps only +:username+ and +:title+ attributes from parameters. # # wrap_parameters false - # # disable parameters wrapping for this controller altogether. + # # disables parameters wrapping for this controller altogether. # # ==== Options # * <tt>:format</tt> - The list of formats in which the parameters wrapper 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/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 2080e9b5b9..258a40aea6 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/class/attribute' +require 'action_controller/metal/exceptions' module ActionController #:nodoc: class InvalidAuthenticityToken < ActionControllerError #:nodoc: @@ -7,17 +8,16 @@ module ActionController #:nodoc: # Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks # by including a token in the rendered html for your application. This token is # stored as a random string in the session, to which an attacker does not have - # access. When a request reaches your application, \Rails then verifies the received - # token with the token in the session. Only HTML and javascript requests are checked, + # access. When a request reaches your application, \Rails verifies the received + # token with the token in the session. Only HTML and JavaScript requests are checked, # so this will not protect your XML API (presumably you'll have a different # authentication scheme there anyway). Also, GET requests are not protected as these # should be idempotent. # # CSRF protection is turned on with the <tt>protect_from_forgery</tt> method, - # which will check the token and raise an ActionController::InvalidAuthenticityToken - # if it doesn't match what was expected. A call to this method is generated for new - # \Rails applications by default. You can customize the error message by editing - # public/422.html. + # which checks the token and resets the session if it doesn't match what was expected. + # A call to this method is generated for new \Rails applications by default. + # You can customize the error message by editing public/422.html. # # The token parameter is named <tt>authenticity_token</tt> by default. The name and # value of this token must be added to every layout that renders forms by including @@ -79,6 +79,8 @@ module ActionController #:nodoc: end end + # This is the method that defines the application behaviour when a request is found to be unverified. + # By default, \Rails resets the session when it finds an unverified request. def handle_unverified_request reset_session end diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index ebadb29ea7..3794e277f6 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -9,7 +9,7 @@ module ActionController #:nodoc: # respond_to :html, :xml, :json # # def index - # @people = Person.find(:all) + # @people = Person.all # respond_with(@people) # end # end @@ -162,6 +162,11 @@ module ActionController #:nodoc: navigation_behavior(e) end + # to_js simply tries to render a template. If no template is found, raises the error. + def to_js + default_render + end + # All other formats follow the procedure below. First we try to render a # template, if the template is not available, we verify if the resource # responds to :to_format and display it. diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 3892a12407..5fe5334458 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -24,20 +24,8 @@ module ActionController #:nodoc: # # == Examples # - # Streaming can be added to a controller easily, all you need to do is - # call +stream+ in the controller class: - # - # class PostsController - # stream - # end - # - # The +stream+ method accepts the same options as +before_filter+ and friends: - # - # class PostsController - # stream :only => :index - # end - # - # You can also selectively turn on streaming for specific actions: + # Streaming can be added to a given template easily, all you need to do is + # to pass the :stream option. # # class PostsController # def index @@ -72,6 +60,9 @@ module ActionController #:nodoc: # render :stream => true # end # + # Notice that :stream only works with templates. Rendering :json + # or :xml with :stream won't work. + # # == Communication between layout and template # # When streaming, rendering happens top-down instead of inside-out. @@ -209,33 +200,9 @@ module ActionController #:nodoc: extend ActiveSupport::Concern include AbstractController::Rendering - attr_internal :stream - - module ClassMethods - # Render streaming templates. It accepts :only, :except, :if and :unless as options - # to specify when to stream, as in ActionController filters. - def stream(options={}) - if defined?(Fiber) - before_filter :_stream_filter, options - else - raise "You cannot use streaming if Fiber is not available." - end - end - end protected - # Mark following render calls as streaming. - def _stream_filter #:nodoc: - self.stream = true - end - - # Consider the stream option when normalazing options. - def _normalize_options(options) #:nodoc: - super - options[:stream] = self.stream unless options.key?(:stream) - end - # Set proper cache control and transfer encoding when streaming def _process_options(options) #:nodoc: super @@ -260,4 +227,3 @@ module ActionController #:nodoc: end end end -
\ No newline at end of file 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/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index 2def78b51a..c11d676c5e 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -40,7 +40,7 @@ module ActionController # dom_class(post, :edit) # => "edit_post" # dom_class(Person, :edit) # => "edit_person" def dom_class(record_or_class, prefix = nil) - singular = ActiveModel::Naming.singular(record_or_class) + singular = ActiveModel::Naming.param_key(record_or_class) prefix ? "#{prefix}#{JOIN}#{singular}" : singular end 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/request.rb b/actionpack/lib/action_dispatch/http/request.rb index b22d426c1f..b80574f497 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -6,6 +6,7 @@ require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/string/access' require 'active_support/inflector' require 'action_dispatch/http/headers' +require 'action_controller/metal/exceptions' module ActionDispatch class Request < Rack::Request 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/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 8cee9ecdc4..1c312f2587 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -129,6 +129,11 @@ module ActionDispatch @cookies[name.to_s] end + def key?(name) + @cookies.key?(name.to_s) + end + alias :has_key? :key? + def update(other_hash) @cookies.update other_hash.stringify_keys self diff --git a/actionpack/lib/action_dispatch/middleware/head.rb b/actionpack/lib/action_dispatch/middleware/head.rb index 56e2d2f2a8..f1906a3ab3 100644 --- a/actionpack/lib/action_dispatch/middleware/head.rb +++ b/actionpack/lib/action_dispatch/middleware/head.rb @@ -8,7 +8,7 @@ module ActionDispatch if env["REQUEST_METHOD"] == "HEAD" env["REQUEST_METHOD"] = "GET" env["rack.methodoverride.original_method"] = "HEAD" - status, headers, body = @app.call(env) + status, headers, _ = @app.call(env) [status, headers, []] else @app.call(env) diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index c17c746096..a765c23dae 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/exception' +require 'action_controller/metal/exceptions' require 'active_support/notifications' require 'action_dispatch/http/request' 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 ec76d1da1e..65895590bf 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -49,6 +49,9 @@ module ActionDispatch class Mapping #:nodoc: IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix] + ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} + SHORTHAND_REGEX = %r{^/[\w/]+$} + WILDCARD_PATH = %r{\*([^/]+)$} def initialize(set, scope, path, options) @set, @scope = set, scope @@ -77,7 +80,7 @@ module ActionDispatch # segment_keys.include?(k.to_s) || k == :controller next unless Regexp === requirement && !constraints[name] - if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} + if requirement.source =~ ANCHOR_CHARACTERS_REGEX raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}" end if requirement.multiline? @@ -88,7 +91,7 @@ module ActionDispatch # match "account/overview" def using_match_shorthand?(path, options) - path && options.except(:via, :anchor, :to, :as).empty? && path =~ %r{^/[\w\/]+$} + path && options.except(:via, :anchor, :to, :as).empty? && path =~ SHORTHAND_REGEX end def normalize_path(path) @@ -107,7 +110,7 @@ module ActionDispatch # Add a constraint for wildcard route to make it non-greedy and match the # optional format part of the route by default - if path.match(/\*([^\/]+)$/) && @options[:format] != false + if path.match(WILDCARD_PATH) && @options[:format] != false @options.reverse_merge!(:"#{$1}" => /.+?/) end @@ -1102,9 +1105,9 @@ module ActionDispatch # # The +comments+ resource here will have the following routes generated for it: # - # post_comments GET /sekret/posts/:post_id/comments(.:format) - # post_comments POST /sekret/posts/:post_id/comments(.:format) - # new_post_comment GET /sekret/posts/:post_id/comments/new(.:format) + # post_comments GET /posts/:post_id/comments(.:format) + # post_comments POST /posts/:post_id/comments(.:format) + # new_post_comment GET /posts/:post_id/comments/new(.:format) # edit_comment GET /sekret/comments/:id/edit(.:format) # comment GET /sekret/comments/:id(.:format) # comment PUT /sekret/comments/:id(.:format) diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index 82c4fadb50..49aef0bf72 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -182,10 +182,12 @@ module ActionDispatch if record.is_a?(Symbol) || record.is_a?(String) route << record - else + elsif record route << ActiveModel::Naming.route_key(record) route = [route.join("_").singularize] if inflection == :singular route << "index" if ActiveModel::Naming.uncountable?(record) && inflection == :plural + else + raise ArgumentError, "Nil location provided. Can't build URI." end route << routing_type(options) diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 5097f6732d..11228c597d 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -4,6 +4,7 @@ require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/module/remove_method' +require 'action_controller/metal/exceptions' module ActionDispatch module Routing diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 57a24a1bd9..b10aab9029 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -1,6 +1,7 @@ require 'uri' require 'active_support/core_ext/hash/diff' require 'active_support/core_ext/hash/indifferent_access' +require 'action_controller/metal/exceptions' module ActionDispatch module Assertions diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 9791ba58e2..aae5752c93 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -207,9 +207,6 @@ module ActionDispatch "*/*;q=0.5" unless defined? @named_routes_configured - # install the named routes in this session instance. - klass = singleton_class - # the helpers are made protected by default--we make them public for # easier access during testing and troubleshooting. @named_routes_configured = true 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_pack/version.rb b/actionpack/lib/action_pack/version.rb index fcf0eb9565..add6b56425 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -1,9 +1,9 @@ module ActionPack module VERSION #:nodoc: MAJOR = 3 - MINOR = 1 + MINOR = 2 TINY = 0 - PRE = "rc1" + PRE = "beta" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') 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..aae8377f8a --- /dev/null +++ b/actionpack/lib/action_view/asset_paths.rb @@ -0,0 +1,147 @@ +require 'zlib' +require 'active_support/core_ext/file' +require 'action_controller/metal/exceptions' + +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 % (Zlib.crc32(source) % 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/base.rb b/actionpack/lib/action_view/base.rb index fd2970b8e2..36a0a20066 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -194,7 +194,7 @@ module ActionView #:nodoc: end def initialize(context = nil, assigns = {}, controller = nil, formats = nil) #:nodoc: - @_config = {} + @_config = ActiveSupport::InheritableOptions.new # Handle all these for backwards compatibility. # TODO Provide a new API for AV::Base and deprecate this one. diff --git a/actionpack/lib/action_view/buffers.rb b/actionpack/lib/action_view/buffers.rb index 089fc68706..be7f65c2ce 100644 --- a/actionpack/lib/action_view/buffers.rb +++ b/actionpack/lib/action_view/buffers.rb @@ -35,9 +35,9 @@ module ActionView def html_safe? true end - + def html_safe self end end -end
\ No newline at end of file +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..509c29844a 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -1,6 +1,7 @@ require 'action_view/helpers/asset_tag_helpers/javascript_tag_helpers' require 'action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers' require 'action_view/helpers/asset_tag_helpers/asset_paths' +require 'action_view/helpers/tag_helper' module ActionView # = Action View Asset Tag Helpers @@ -191,6 +192,7 @@ module ActionView # RewriteEngine On # RewriteRule ^/release-\d+/(images|javascripts|stylesheets)/(.*)$ /$1/$2 [L] module AssetTagHelper + include TagHelper include JavascriptTagHelpers include StylesheetTagHelpers # Returns a link tag that browsers and news readers can use to auto-detect @@ -347,7 +349,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 +364,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..8b35aa8896 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,11 @@ +require 'thread' 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/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb index a087688a2c..39c37b25dc 100644 --- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb @@ -20,7 +20,7 @@ module ActionView # # GET /posts.html # # GET /posts.atom # def index - # @posts = Post.find(:all) + # @posts = Post.all # # respond_to do |format| # format.html diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index e81d03b537..f81ce3e31c 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -51,7 +51,11 @@ module ActionView # This dance is needed because Builder can't use capture pos = output_buffer.length yield + 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 end diff --git a/actionpack/lib/action_view/helpers/controller_helper.rb b/actionpack/lib/action_view/helpers/controller_helper.rb index db59bca159..1a583e62ae 100644 --- a/actionpack/lib/action_view/helpers/controller_helper.rb +++ b/actionpack/lib/action_view/helpers/controller_helper.rb @@ -20,4 +20,4 @@ module ActionView end end end -end
\ No newline at end of file +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/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb index cd67851642..c0cc7d347c 100644 --- a/actionpack/lib/action_view/helpers/debug_helper.rb +++ b/actionpack/lib/action_view/helpers/debug_helper.rb @@ -30,7 +30,7 @@ module ActionView begin Marshal::dump(object) "<pre class='debug_dump'>#{h(object.to_yaml).gsub(" ", " ")}</pre>".html_safe - rescue Exception => e # errors from Marshal or YAML + rescue Exception # errors from Marshal or YAML # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback "<code class='debug_dump'>#{h(object.inspect)}</code>".html_safe end diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index cb1c13912a..3a30263b49 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -4,6 +4,7 @@ require 'action_view/helpers/tag_helper' require 'action_view/helpers/form_tag_helper' require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/hash/slice' +require 'active_support/core_ext/module/method_names' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/string/output_safety' require 'active_support/core_ext/array/extract_options' @@ -202,13 +203,13 @@ module ActionView # # is equivalent to something like: # - # <%= form_for @post, :as => :post, :url => post_path(@post), :html => { :class => "new_post", :id => "new_post" } do |f| %> + # <%= form_for @post, :as => :post, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %> # ... # <% end %> # # You can also overwrite the individual conventions, like this: # - # <%= form_for(@post, :url => super_post_path(@post)) do |f| %> + # <%= form_for(@post, :url => super_posts_path) do |f| %> # ... # <% end %> # @@ -219,9 +220,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 +291,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 +518,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: @@ -555,19 +568,6 @@ module ActionView # ... # <% end %> # - # In addition, you may want to have access to the current iteration index. - # In that case, you can use a similar method called fields_for_with_index - # which receives a block with an extra parameter: - # - # <%= form_for @person do |person_form| %> - # ... - # <%= person_form.fields_for_with_index :projects do |project_fields, index| %> - # Position: <%= index %> - # Name: <%= project_fields.text_field :name %> - # <% end %> - # ... - # <% end %> - # # When projects is already an association on Person you can use # +accepts_nested_attributes_for+ to define the writer method for you: # @@ -1149,7 +1149,7 @@ module ActionView options["name"] ||= tag_name_with_index(@auto_index) options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) } else - options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '') + options["name"] ||= tag_name + (options['multiple'] ? '[]' : '') options["id"] = options.fetch("id"){ tag_id } end end @@ -1229,15 +1229,8 @@ module ActionView RUBY_EVAL end - # Check +fields_for+ for docs and examples. - def fields_for_with_index(record_name, record_object = nil, fields_options = {}, &block) - index = fields_options[:index] || options[:child_index] || nested_child_index(@object_name) - block_with_index = Proc.new{ |obj| block.call(obj, index) } - fields_for(record_name, record_object, fields_options, &block_with_index) - 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 794132637c..7c43dc04e0 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -105,7 +105,10 @@ module ActionView # Create a select tag and a series of contained option tags for the provided object and method. # The option currently held by the object will be selected, provided that the object is available. - # See options_for_select for the required format of the choices parameter. + # + # There are two possible formats for the choices parameter, corresponding to other helpers' output: + # * A flat collection: see options_for_select + # * A nested collection: see grouped_options_for_select # # Example with @post.person_id => 1: # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { :include_blank => true }) @@ -128,6 +131,28 @@ module ActionView # By default, <tt>post.person_id</tt> is the selected option. Specify <tt>:selected => value</tt> to use a different selection # or <tt>:selected => nil</tt> to leave all options unselected. Similarly, you can specify values to be disabled in the option # tags by specifying the <tt>:disabled</tt> option. This can either be a single value or an array of values to be disabled. + # + # ==== Gotcha + # + # The HTML specification says when +multiple+ parameter passed to select and all options got deselected + # web browsers do not send any value to server. Unfortunately this introduces a gotcha: + # if an +User+ model has many +roles+ and have +role_ids+ accessor, and in the form that edits roles of the user + # the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So, + # any mass-assignment idiom like + # + # @user.update_attributes(params[:user]) + # + # wouldn't update roles. + # + # To prevent this the helper generates an auxiliary hidden field before + # every multiple select. The hidden field has the same name as multiple select and blank value. + # + # This way, the client either sends only the hidden field (representing + # the deselected multiple select box), or both fields. Since the HTML specification + # says key/value pairs have to be sent in the same order they appear in the + # form, and parameters extraction gets the last occurrence of any repeated + # key in the query string, that works for ordinary forms. + # def select(object, method, choices, options = {}, html_options = {}) InstanceTag.new(object, method, self, options.delete(:object)).to_select_tag(choices, options, html_options) end @@ -427,7 +452,7 @@ module ActionView # # Sample usage (Hash): # grouped_options = { - # 'North America' => [['United States','US], 'Canada'], + # 'North America' => [['United States','US'], 'Canada'], # 'Europe' => ['Denmark','Germany','France'] # } # grouped_options_for_select(grouped_options) @@ -552,43 +577,33 @@ 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 - content_tag("select", 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) + + if !choices.empty? && choices.try(:first).try(:second).respond_to?(:each) + option_tags = grouped_options_for_select(choices, :selected => selected_value, :disabled => options[:disabled]) + else + option_tags = options_for_select(choices, :selected => selected_value, :disabled => options[:disabled]) + end + + select_content_tag(option_tags, 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 - content_tag( - "select", add_options(options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => disabled_value), options, value), html_options + selected_value = options.has_key?(:selected) ? options[:selected] : value(object) + select_content_tag( + 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) - content_tag( - "select", 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 + select_content_tag( + 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) - content_tag("select", - add_options( - time_zone_options_for_select(value || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone), - options, value - ), html_options + select_content_tag( + time_zone_options_for_select(value(object) || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone), options, html_options ) end @@ -603,6 +618,17 @@ module ActionView end option_tags.html_safe end + + 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 + select + end + end end class FormBuilder diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index a91e86f4db..72bc4510b5 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -597,6 +597,12 @@ module ActionView number_field_tag(name, value, options.stringify_keys.update("type" => "range")) end + # Creates the hidden UTF8 enforcer tag. Override this method in a helper + # to customize the tag. + def utf8_enforcer_tag + tag(:input, :type => "hidden", :name => "utf8", :value => "✓".html_safe) + end + private def html_options_for_form(url_for_options, options, *parameters_for_url) options.stringify_keys.tap do |html_options| @@ -611,9 +617,6 @@ module ActionView end def extra_tags_for_form(html_options) - snowman_tag = tag(:input, :type => "hidden", - :name => "utf8", :value => "✓".html_safe) - authenticity_token = html_options.delete("authenticity_token") method = html_options.delete("method").to_s @@ -629,7 +632,7 @@ module ActionView tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag(authenticity_token) end - tags = snowman_tag << method_tag + tags = utf8_enforcer_tag << method_tag content_tag(:div, tags, :style => 'margin:0;padding:0;display:inline') end diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index d7228bab67..4484390fde 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -18,7 +18,8 @@ module ActionView # $('some_element').replaceWith('<%=j render 'some/element_template' %>'); def escape_javascript(javascript) if javascript - javascript.gsub(/(\\|<\/|\r\n|[\n\r"'])/) { JS_ESCAPE_MAP[$1] } + result = javascript.gsub(/(\\|<\/|\r\n|[\n\r"'])/) {|match| JS_ESCAPE_MAP[match] } + javascript.html_safe? ? result.html_safe : result else '' end diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 63d13a0f0b..fe0288521f 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -211,7 +211,7 @@ module ActionView defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {}) options = options.reverse_merge(defaults) - parts = number.to_s.split('.') + parts = number.to_s.to_str.split('.') parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}") parts.join(options[:separator]).html_safe @@ -342,7 +342,7 @@ module ActionView options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros) storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true) - + base = options[:prefix] == :si ? 1000 : 1024 if number.to_i < base diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 75422a343d..ae71ade588 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -255,9 +255,11 @@ 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 text.gsub!(/\r\n?/, "\n") # \r\n and \r -> \n text.gsub!(/\n\n+/, "</p>\n\n#{start_tag}") # 2+ newline -> paragraph text.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br @@ -279,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 25411856cb..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 # @@ -497,14 +497,14 @@ module ActionView }.compact extras = extras.empty? ? '' : '?' + ERB::Util.html_escape(extras.join('&')) - email_address_obfuscated = email_address.dup + email_address_obfuscated = email_address.to_str email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.key?("replace_at") email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.key?("replace_dot") case encode when "javascript" string = '' html = content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe)) - html = escape_javascript(html) + html = escape_javascript(html.to_str) "document.write('#{html}');".each_byte do |c| string << sprintf("%%%x", c) end @@ -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/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index f0ed3425de..560df15e82 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -10,7 +10,7 @@ module ActionView # this key is generated just once during the request, it speeds up all cache accesses. class LookupContext #:nodoc: attr_accessor :prefixes - + mattr_accessor :fallbacks @@fallbacks = FallbackFileSystemResolver.instances diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb index a09cef8fef..d04c00fd40 100644 --- a/actionpack/lib/action_view/renderer/template_renderer.rb +++ b/actionpack/lib/action_view/renderer/template_renderer.rb @@ -64,7 +64,7 @@ module ActionView layout =~ /^\// ? with_fallbacks { find_template(layout, nil, false, keys) } : find_template(layout, nil, false, keys) end - rescue ActionView::MissingTemplate => e + rescue ActionView::MissingTemplate update_details(:formats => nil) do raise unless template_exists?(layout) end diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb index d4448a7b33..587e37a84f 100644 --- a/actionpack/lib/action_view/template/error.rb +++ b/actionpack/lib/action_view/template/error.rb @@ -31,7 +31,6 @@ module ActionView def initialize(paths, path, prefixes, partial, details, *) @path = path prefixes = Array.wrap(prefixes) - display_paths = paths.compact.map{ |p| p.to_s.inspect }.join(", ") template_type = if partial "partial" elsif path =~ /layouts/i diff --git a/actionpack/lib/action_view/testing/resolvers.rb b/actionpack/lib/action_view/testing/resolvers.rb index 773dfcbb1d..7afa2fa613 100644 --- a/actionpack/lib/action_view/testing/resolvers.rb +++ b/actionpack/lib/action_view/testing/resolvers.rb @@ -34,7 +34,7 @@ module ActionView #:nodoc: templates << Template.new(source, _path, handler, :virtual_path => path.virtual, :format => format, :updated_at => updated_at) end - + templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size } end end 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/compressors.rb b/actionpack/lib/sprockets/compressors.rb new file mode 100644 index 0000000000..6544953df4 --- /dev/null +++ b/actionpack/lib/sprockets/compressors.rb @@ -0,0 +1,21 @@ +module Sprockets + class NullCompressor + def compress(content) + content + end + end + + class LazyCompressor + def initialize(&block) + @block = block + end + + def compressor + @compressor ||= @block.call || NullCompressor.new + end + + def compress(content) + compressor.compress(content) + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/sprockets/helpers/rails_helper.rb b/actionpack/lib/sprockets/helpers/rails_helper.rb index dcf4b9279e..a95e7c0649 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" module Sprockets module Helpers @@ -10,8 +9,12 @@ module Sprockets def asset_paths @asset_paths ||= begin config = self.config if respond_to?(:config) + config ||= Rails.application.config controller = self.controller if respond_to?(:controller) - RailsHelper::AssetPaths.new(config, controller) + paths = RailsHelper::AssetPaths.new(config, controller) + paths.asset_environment = asset_environment + paths.asset_prefix = asset_prefix + paths end end @@ -51,7 +54,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,35 +62,59 @@ 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, 'assets', default_ext, true) - body ? "#{path}?body=1" : path + # Override to specify an alternative prefix for asset path generation. + # When combined with a custom +asset_environment+, this can be used to + # implement themes that can take advantage of the asset pipeline. + # + # If you only want to change where the assets are mounted, refer to + # +config.assets.prefix+ instead. + def asset_prefix + Rails.application.config.assets.prefix end - class AssetPaths < ActionView::Helpers::AssetPaths #:nodoc: - def compute_public_path(source, dir, ext=nil, include_host=true) - super(source, 'assets', ext, include_host) + # Override to specify an alternative asset environment for asset + # path generation. The environment should already have been mounted + # at the prefix returned by +asset_prefix+. + def asset_environment + Rails.application.assets + end + + class AssetPaths < ::ActionView::AssetPaths #:nodoc: + attr_accessor :asset_environment, :asset_prefix + + def compute_public_path(source, dir, ext=nil, include_host=true, protocol=nil) + super(source, asset_prefix, ext, include_host, protocol) + end + + # Return the filesystem path for the source + def compute_source_path(source, ext) + asset_for(source, ext) end def asset_for(source, ext) source = source.to_s return nil if is_uri?(source) source = rewrite_extension(source, nil, ext) - assets[source] + asset_environment[source] end def rewrite_asset_path(source, dir) if source[0] == ?/ source else - assets.path(source, performing_caching?, dir) + asset_environment.path(source, performing_caching?, dir) end end @@ -99,13 +126,9 @@ module Sprockets end end - def assets - Rails.application.assets - end - # 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 7b8a7ad3bb..4906ad9a9c 100644 --- a/actionpack/lib/sprockets/railtie.rb +++ b/actionpack/lib/sprockets/railtie.rb @@ -1,15 +1,15 @@ module Sprockets autoload :Helpers, "sprockets/helpers" + autoload :LazyCompressor, "sprockets/compressors" + autoload :NullCompressor, "sprockets/compressors" + # TODO: Get rid of config.assets.enabled class Railtie < ::Rails::Railtie - def self.using_coffee? - require 'coffee-script' - defined?(CoffeeScript) - rescue LoadError - false - end + config.default_asset_host_protocol = :relative - config.app_generators.javascript_engine :coffee if using_coffee? + rake_tasks do + load "sprockets/assets.rake" + end # Configure ActionController to use sprockets. initializer "sprockets.set_configs", :after => "action_controller.set_configs" do |app| @@ -46,43 +46,66 @@ module Sprockets end protected + def asset_environment(app) + require "sprockets" - def asset_environment(app) - require "sprockets" - assets = app.config.assets - env = Sprockets::Environment.new(app.root.to_s) - env.static_root = File.join(app.root.join("public"), assets.prefix) - env.paths.concat assets.paths - env.logger = Rails.logger - env.js_compressor = expand_js_compressor(assets.js_compressor) if assets.compress - env.css_compressor = expand_css_compressor(assets.css_compressor) if assets.compress - env - end + assets = app.config.assets + + env = Sprockets::Environment.new(app.root.to_s) + + env.static_root = File.join(app.root.join("public"), assets.prefix) + + 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 + + if env.respond_to?(:cache) && assets.cache_store != false + env.cache = ActiveSupport::Cache.lookup_store(assets.cache_store) || 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. + unless assets.js_compressor == false + env.js_compressor = LazyCompressor.new { expand_js_compressor(assets.js_compressor || :uglifier) } + end - def expand_js_compressor(sym) - case sym - when :closure - require 'closure-compiler' - Closure::Compiler.new - when :uglifier - require 'uglifier' - Uglifier.new - when :yui - require 'yui/compressor' - YUI::JavaScriptCompressor.new - else - sym + unless assets.css_compressor == false + env.css_compressor = LazyCompressor.new { expand_css_compressor(assets.css_compressor) } + end + end + + env end - end - def expand_css_compressor(sym) - case sym - when :yui - require 'yui/compressor' - YUI::CssCompressor.new - else - sym + def expand_js_compressor(sym) + case sym + when :closure + require 'closure-compiler' + Closure::Compiler.new + when :uglifier + require 'uglifier' + Uglifier.new + when :yui + require 'yui/compressor' + YUI::JavaScriptCompressor.new + else + sym + end + end + + def expand_css_compressor(sym) + case sym + when :yui + require 'yui/compressor' + YUI::CssCompressor.new + else + sym + end end - end end end diff --git a/actionpack/test/abstract/abstract_controller_test.rb b/actionpack/test/abstract/abstract_controller_test.rb index 981c023d38..5823e64637 100644 --- a/actionpack/test/abstract/abstract_controller_test.rb +++ b/actionpack/test/abstract/abstract_controller_test.rb @@ -178,16 +178,10 @@ module AbstractController end end - class Me5 < WithLayouts - def index - render - end - end - class TestLayouts < ActiveSupport::TestCase test "layouts are included" do controller = Me4.new - result = controller.process(:index) + controller.process(:index) assert_equal "Me4 Enter : Hello from me4/index.erb : Exit", controller.response_body end end @@ -241,11 +235,11 @@ module AbstractController assert_dispatch ActionMissingRespondToActionController, "success", :ohai end - test "a method is available as an action if respond_to_action? returns true" do + test "a method is available as an action if method_for_action returns true" do assert_dispatch RespondToActionController, "success", :index end - test "raises ActionNotFound if method is defined but respond_to_action? returns false" do + test "raises ActionNotFound if method is defined but method_for_action returns false" do assert_raise(ActionNotFound) { RespondToActionController.new.process(:fail) } end end diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb index 3bdde86291..5d1a703c55 100644 --- a/actionpack/test/abstract/callbacks_test.rb +++ b/actionpack/test/abstract/callbacks_test.rb @@ -131,7 +131,7 @@ module AbstractController end def authenticate - @list = [] + @list ||= [] @authenticated = "true" end end diff --git a/actionpack/test/abstract/layouts_test.rb b/actionpack/test/abstract/layouts_test.rb index 5ed6aa68b5..86208899f8 100644 --- a/actionpack/test/abstract/layouts_test.rb +++ b/actionpack/test/abstract/layouts_test.rb @@ -87,18 +87,6 @@ module AbstractControllerTests end end - class WithSymbolReturningString < Base - layout :no_hello - - def index - render :template => ActionView::Template::Text.new("Hello missing symbol!") - end - private - def no_hello - nil - end - end - class WithSymbolReturningNil < Base layout :nilz diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 60534a9746..24d071df39 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -151,6 +151,7 @@ class BasicController config.assets_dir = public_dir config.javascripts_dir = "#{public_dir}/javascripts" config.stylesheets_dir = "#{public_dir}/stylesheets" + config.assets = ActiveSupport::InheritableOptions.new({ :prefix => "assets" }) config end end diff --git a/actionpack/test/activerecord/active_record_store_test.rb b/actionpack/test/activerecord/active_record_store_test.rb index f0fb113860..768ac713ca 100644 --- a/actionpack/test/activerecord/active_record_store_test.rb +++ b/actionpack/test/activerecord/active_record_store_test.rb @@ -225,6 +225,36 @@ class ActiveRecordStoreTest < ActionDispatch::IntegrationTest assert_equal session_id, cookies['_session_id'] end end + + def test_incoming_invalid_session_id_via_cookie_should_be_ignored + with_test_route_set do + open_session do |sess| + sess.cookies['_session_id'] = 'INVALID' + + sess.get '/set_session_value' + new_session_id = sess.cookies['_session_id'] + assert_not_equal 'INVALID', new_session_id + + sess.get '/get_session_value' + new_session_id_2 = sess.cookies['_session_id'] + assert_equal new_session_id, new_session_id_2 + end + end + end + + def test_incoming_invalid_session_id_via_parameter_should_be_ignored + with_test_route_set(:cookie_only => false) do + open_session do |sess| + sess.get '/set_session_value', :_session_id => 'INVALID' + new_session_id = sess.cookies['_session_id'] + assert_not_equal 'INVALID', new_session_id + + sess.get '/get_session_value' + new_session_id_2 = sess.cookies['_session_id'] + assert_equal new_session_id, new_session_id_2 + end + end + end private @@ -247,6 +277,7 @@ class ActiveRecordStoreTest < ActionDispatch::IntegrationTest session_class, ActiveRecord::SessionStore.session_class = ActiveRecord::SessionStore.session_class, "ActiveRecord::SessionStore::#{class_name.camelize}".constantize yield + ensure ActiveRecord::SessionStore.session_class = session_class end end diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb index f9e47d5118..20d11377f6 100644 --- a/actionpack/test/activerecord/polymorphic_routes_test.rb +++ b/actionpack/test/activerecord/polymorphic_routes_test.rb @@ -87,6 +87,14 @@ class PolymorphicRoutesTest < ActionController::TestCase end end + def test_with_nil + with_test_routes do + assert_raise ArgumentError, "Nil location provided. Can't build URI." do + polymorphic_url(nil) + end + end + end + def test_with_record with_test_routes do @project.save diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index 7f3d943bba..a714e8bbcc 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -100,9 +100,6 @@ class AssertResponseWithUnexpectedErrorController < ActionController::Base end end -class UserController < ActionController::Base -end - module Admin class InnerModuleController < ActionController::Base def index diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 82c2c23607..da3314fe6d 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -127,7 +127,7 @@ class PageCachingTest < ActionController::TestCase assert_equal 'I am xml', @response.body end - def test_should_cache_with_trailing_slash_on_url + def test_cached_page_should_not_have_trailing_slash_even_if_url_has_trailing_slash @controller.class.cache_page 'cached content', '/page_caching_test/trailing_slash/' assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/trailing_slash.html") end @@ -141,7 +141,7 @@ class PageCachingTest < ActionController::TestCase [:ok, :no_content, :found, :not_found].each do |status| [:get, :post, :put, :delete].each do |method| - unless method == :get and status == :ok + unless method == :get && status == :ok define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do send(method, status) assert_response status diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb index b12c798302..d51882066d 100644 --- a/actionpack/test/controller/content_type_test.rb +++ b/actionpack/test/controller/content_type_test.rb @@ -74,6 +74,7 @@ class ContentTypeTest < ActionController::TestCase get :render_defaults assert_equal "utf-16", @response.charset assert_equal Mime::HTML, @response.content_type + ensure OldContentTypeController.default_charset = "utf-8" 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/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index 26270571cf..afb2d39955 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -509,7 +509,7 @@ end class RespondWithController < ActionController::Base respond_to :html, :json respond_to :xml, :except => :using_resource_with_block - respond_to :js, :only => [ :using_resource_with_block, :using_resource ] + respond_to :js, :only => [ :using_resource_with_block, :using_resource, :using_hash_resource ] def using_resource respond_with(resource) @@ -575,11 +575,6 @@ protected def resource Customer.new("david", request.delete? ? nil : 13) end - - def _render_js(js, options) - self.content_type ||= Mime::JS - self.response_body = js.respond_to?(:to_js) ? js.to_js : js - end end class InheritedRespondWithController < RespondWithController @@ -638,6 +633,20 @@ class RespondWithControllerTest < ActionController::TestCase end end + def test_using_resource_with_js_simply_tries_to_render_the_template + @request.accept = "text/javascript" + get :using_resource + assert_equal "text/javascript", @response.content_type + assert_equal "alert(\"Hi\");", @response.body + end + + def test_using_hash_resource_with_js_raises_an_error_if_template_cant_be_found + @request.accept = "text/javascript" + assert_raise ActionView::MissingTemplate do + get :using_hash_resource + end + end + def test_using_hash_resource @request.accept = "application/xml" get :using_hash_resource @@ -650,6 +659,13 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal %Q[{"result":{"name":"david","id":13}}], @response.body end + def test_using_hash_resource_with_post + @request.accept = "application/json" + assert_raise ArgumentError, "Nil location provided. Can't build URI." do + post :using_hash_resource + end + end + def test_using_resource_with_block @request.accept = "*/*" get :using_resource_with_block diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb index 48cf0ab9cb..1a17e24914 100644 --- a/actionpack/test/controller/new_base/render_streaming_test.rb +++ b/actionpack/test/controller/new_base/render_streaming_test.rb @@ -10,9 +10,9 @@ module RenderStreaming )] layout "application" - stream :only => [:hello_world, :skip] def hello_world + render :stream => true end def layout_exception 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 f48b73b63a..043d44500a 100644 --- a/actionpack/test/controller/test_test.rb +++ b/actionpack/test/controller/test_test.rb @@ -612,7 +612,6 @@ XML send(method, :test_remote_addr) assert false, "expected RuntimeError, got nothing" rescue RuntimeError => error - assert true assert_match(%r{@#{variable} is nil}, error.message) rescue => error assert false, "expected RuntimeError, got #{error.class}" @@ -650,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/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index 3f3d6dcc2f..484e996f31 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -293,8 +293,8 @@ module AbstractController first_class.default_url_options[:host] = first_host second_class.default_url_options[:host] = second_host - assert_equal first_class.default_url_options[:host], first_host - assert_equal second_class.default_url_options[:host], second_host + assert_equal first_host, first_class.default_url_options[:host] + assert_equal second_host, second_class.default_url_options[:host] end def test_with_stringified_keys diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb index 621fb79915..ae8588cbb0 100644 --- a/actionpack/test/controller/webservice_test.rb +++ b/actionpack/test/controller/webservice_test.rb @@ -30,7 +30,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest def test_check_parameters with_test_route_set do get "/" - assert_blank @controller.response.body + assert_equal '', @controller.response.body end end @@ -162,7 +162,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest def test_use_xml_ximple_with_empty_request with_test_route_set do assert_nothing_raised { post "/", "", {'CONTENT_TYPE' => 'application/xml'} } - assert_blank @controller.response.body + assert_equal '', @controller.response.body end end diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 8e5fd97cc6..fb67ecb07d 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -148,6 +148,15 @@ class CookiesTest < ActionController::TestCase @request.host = "www.nextangle.com" end + def test_key_methods + assert !request.cookie_jar.key?(:foo) + assert !request.cookie_jar.has_key?("foo") + + request.cookie_jar[:foo] = :bar + assert request.cookie_jar.key?(:foo) + assert request.cookie_jar.has_key?("foo") + end + def test_setting_cookie get :authenticate assert_cookie_header "user_name=david; path=/" 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/session/test_session_test.rb b/actionpack/test/dispatch/session/test_session_test.rb index 31ce97a25b..904398f563 100644 --- a/actionpack/test/dispatch/session/test_session_test.rb +++ b/actionpack/test/dispatch/session/test_session_test.rb @@ -29,7 +29,6 @@ class ActionController::TestSessionTest < ActiveSupport::TestCase end def test_clear_emptys_session - params = {:one => 'one', :two => 'two'} session = ActionController::TestSession.new({:one => 'one', :two => 'two'}) session.clear assert_nil(session[:one]) 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/fixtures/respond_with/using_resource.js.erb b/actionpack/test/fixtures/respond_with/using_resource.js.erb new file mode 100644 index 0000000000..4417680bce --- /dev/null +++ b/actionpack/test/fixtures/respond_with/using_resource.js.erb @@ -0,0 +1 @@ +alert("Hi");
\ No newline at end of file diff --git a/actionpack/test/fixtures/sprockets/alternate/stylesheets/style.css b/actionpack/test/fixtures/sprockets/alternate/stylesheets/style.css new file mode 100644 index 0000000000..bfb90bfa48 --- /dev/null +++ b/actionpack/test/fixtures/sprockets/alternate/stylesheets/style.css @@ -0,0 +1 @@ +/* Different from other style.css */
\ No newline at end of file diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb index 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..d5bd7256f7 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -1,3 +1,4 @@ +require 'zlib' require 'abstract_unit' require 'active_support/ordered_options' @@ -424,6 +425,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 @@ -681,9 +690,9 @@ class AssetTagHelperTest < ActionView::TestCase @controller.config.asset_host = 'http://a%d.example.com' config.perform_caching = true - hash = '/javascripts/cache/money.js'.hash % 4 + number = Zlib.crc32('/javascripts/cache/money.js') % 4 assert_dom_equal( - %(<script src="http://a#{hash}.example.com/javascripts/cache/money.js" type="text/javascript"></script>), + %(<script src="http://a#{number}.example.com/javascripts/cache/money.js" type="text/javascript"></script>), javascript_include_tag(:all, :cache => "cache/money") ) @@ -854,7 +863,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 +973,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 +1100,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 +1134,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/erb_util_test.rb b/actionpack/test/template/erb_util_test.rb index 30f6d1a213..790ab1c74c 100644 --- a/actionpack/test/template/erb_util_test.rb +++ b/actionpack/test/template/erb_util_test.rb @@ -16,6 +16,16 @@ class ErbUtilTest < Test::Unit::TestCase end end + def test_json_escape_returns_unsafe_strings_when_passed_unsafe_strings + value = json_escape("asdf") + assert !value.html_safe? + end + + def test_json_escape_returns_safe_strings_when_passed_safe_strings + value = json_escape("asdf".html_safe) + assert value.html_safe? + end + def test_html_escape_is_html_safe escaped = h("<p>") assert_equal "<p>", escaped diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index f2a49f7a68..cc3d2cddf7 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -696,13 +696,13 @@ class FormHelperTest < ActionView::TestCase concat f.submit('Edit post') end - expected = - "<form accept-charset='UTF-8' action='/posts/44' method='post'>" + - snowman + - "<label for='post_title'>The Title</label>" + + expected = whole_form("/posts/44", "edit_post_44" , "edit_post", :method => "put") do "<input name='post[title]' size='30' type='text' id='post_title' value='And his name will be forty and four.' />" + - "<input name='commit' id='post_submit' type='submit' value='Edit post' />" + + "<input name='commit' type='submit' value='Edit post' />" + "</form>" + end + + assert_dom_equal expected, output_buffer end def test_form_for_with_symbol_object_name @@ -974,22 +974,6 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end - def test_nested_fields_for_with_index_with_index_and_parent_fields - form_for(@post, :index => 1) do |c| - concat c.text_field(:title) - concat c.fields_for_with_index('comment', @comment, :index => 1) { |r, comment_index| - concat r.text_field(:name, "data-index" => comment_index) - } - end - - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'put') do - "<input name='post[1][title]' size='30' type='text' id='post_1_title' value='Hello World' />" + - "<input name='post[1][comment][1][name]' size='30' type='text' id='post_1_comment_1_name' value='new comment' data-index='1' />" - end - - assert_dom_equal expected, output_buffer - end - def test_form_for_with_index_and_nested_fields_for output_buffer = form_for(@post, :index => 1) do |f| concat f.fields_for(:comment, @post) { |c| @@ -1046,20 +1030,6 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end - def test_nested_fields_for_with_index_with_index_radio_button - form_for(@post) do |f| - concat f.fields_for_with_index(:comment, @post, :index => 5) { |c, index| - concat c.radio_button(:title, "hello", "data-index" => index) - } - end - - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'put') do - "<input name='post[comment][5][title]' type='radio' id='post_comment_5_title_hello' value='hello' data-index='5' />" - end - - assert_dom_equal expected, output_buffer - end - def test_nested_fields_for_with_auto_index_on_both form_for(@post, :as => "post[]") do |f| concat f.fields_for("comment[]", @post) { |c| @@ -1259,29 +1229,6 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end - def test_nested_fields_for_with_index_with_existing_records_on_a_nested_attributes_collection_association - @post.comments = Array.new(2) { |id| Comment.new(id + 1) } - - form_for(@post) do |f| - concat f.text_field(:title) - @post.comments.each do |comment| - concat f.fields_for_with_index(:comments, comment) { |cf, index| - concat cf.text_field(:name, "data-index" => index) - } - end - end - - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" data-index="0" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" data-index="1" />' + - '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' - end - - assert_dom_equal expected, output_buffer - end - def test_nested_fields_for_with_existing_records_on_a_nested_attributes_collection_association_with_disabled_hidden_id @post.comments = Array.new(2) { |id| Comment.new(id + 1) } @post.author = Author.new(321) @@ -1309,33 +1256,6 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end - def test_nested_fields_for_with_index_with_existing_records_on_a_nested_attributes_collection_association_with_disabled_hidden_id - @post.comments = Array.new(2) { |id| Comment.new(id + 1) } - @post.author = Author.new(321) - - form_for(@post) do |f| - concat f.text_field(:title) - concat f.fields_for(:author) { |af| - concat af.text_field(:name) - } - @post.comments.each do |comment| - concat f.fields_for_with_index(:comments, comment, :include_id => false) { |cf, index| - concat cf.text_field(:name, 'data-index' => index) - } - end - end - - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + - '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" data-index="0" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" data-index="1" />' - end - - assert_dom_equal expected, output_buffer - end - def test_nested_fields_for_with_existing_records_on_a_nested_attributes_collection_association_with_disabled_hidden_id_inherited @post.comments = Array.new(2) { |id| Comment.new(id + 1) } @post.author = Author.new(321) @@ -1457,28 +1377,6 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end - def test_nested_fields_for_with_index_with_new_records_on_a_nested_attributes_collection_association - @post.comments = [Comment.new, Comment.new] - - form_for(@post) do |f| - concat f.text_field(:title) - @post.comments.each do |comment| - concat f.fields_for_with_index(:comments, comment) { |cf, index| - concat cf.text_field(:name, "data-index" => index) - } - end - end - - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="new comment" data-index="0" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" data-index="1" />' - end - - assert_dom_equal expected, output_buffer - end - - def test_nested_fields_for_with_existing_and_new_records_on_a_nested_attributes_collection_association @post.comments = [Comment.new(321), Comment.new] @@ -1501,29 +1399,6 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end - def test_nested_fields_for_with_index_with_existing_and_new_records_on_a_nested_attributes_collection_association - @post.comments = [Comment.new(321), Comment.new] - - form_for(@post) do |f| - concat f.text_field(:title) - @post.comments.each do |comment| - concat f.fields_for_with_index(:comments, comment) { |cf, index| - concat cf.text_field(:name, "data-index" => index) - } - end - end - - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" data-index="0" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" data-index="1" />' - end - - assert_dom_equal expected, output_buffer - end - - def test_nested_fields_for_with_an_empty_supplied_attributes_collection form_for(@post) do |f| concat f.text_field(:title) @@ -1689,6 +1564,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) @@ -1890,7 +1781,7 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end - def snowman(method = nil) + def hidden_fields(method = nil) txt = %{<div style="margin:0;padding:0;display:inline">} txt << %{<input name="utf8" type="hidden" value="✓" />} if method && !method.to_s.in?(['get', 'post']) @@ -1918,7 +1809,7 @@ class FormHelperTest < ActionView::TestCase method = options end - form_text(action, id, html_class, remote, multipart, method) + snowman(method) + contents + "</form>" + form_text(action, id, html_class, remote, multipart, method) + hidden_fields(method) + contents + "</form>" end def test_default_form_builder diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index b92e1d9890..6aea991f7c 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -378,6 +378,49 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_select_without_multiple + assert_dom_equal( + "<select id=\"post_category\" name=\"post[category]\"></select>", + select(:post, :category, "", {}, :multiple => false) + ) + end + + def test_select_with_grouped_collection_as_nested_array + @post = Post.new + + countries_by_continent = [ + ["<Africa>", [["<South Africa>", "<sa>"], ["Somalia", "so"]]], + ["Europe", [["Denmark", "dk"], ["Ireland", "ie"]]], + ] + + assert_dom_equal( + [ + %Q{<select id="post_origin" name="post[origin]"><optgroup label="<Africa>"><option value="<sa>"><South Africa></option>}, + %Q{<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk">Denmark</option>}, + %Q{<option value="ie">Ireland</option></optgroup></select>}, + ].join("\n"), + select("post", "origin", countries_by_continent) + ) + end + + def test_select_with_grouped_collection_as_hash + @post = Post.new + + countries_by_continent = { + "<Africa>" => [["<South Africa>", "<sa>"], ["Somalia", "so"]], + "Europe" => [["Denmark", "dk"], ["Ireland", "ie"]], + } + + assert_dom_equal( + [ + %Q{<select id="post_origin" name="post[origin]"><optgroup label="<Africa>"><option value="<sa>"><South Africa></option>}, + %Q{<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk">Denmark</option>}, + %Q{<option value="ie">Ireland</option></optgroup></select>}, + ].join("\n"), + select("post", "origin", countries_by_continent) + ) + end + def test_select_with_boolean_method @post = Post.new @post.allow_comments = false @@ -457,6 +500,22 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_select_with_multiple_to_add_hidden_input + output_buffer = select(:post, :category, "", {}, :multiple => true) + assert_dom_equal( + "<input type=\"hidden\" name=\"post[category][]\" value=\"\"/><select multiple=\"multiple\" id=\"post_category\" name=\"post[category][]\"></select>", + output_buffer + ) + end + + def test_select_with_multiple_and_disabled_to_add_disabled_hidden_input + output_buffer = select(:post, :category, "", {}, :multiple => true, :disabled => true) + assert_dom_equal( + "<input disabled=\"disabled\"type=\"hidden\" name=\"post[category][]\" value=\"\"/><select multiple=\"multiple\" disabled=\"disabled\" id=\"post_category\" name=\"post[category][]\"></select>", + output_buffer + ) + end + def test_select_with_blank @post = Post.new @post.category = "<mus>" @@ -649,11 +708,11 @@ class FormOptionsHelperTest < ActionView::TestCase ) end - def test_collection_select_with_multiple_option_appends_array_brackets + def test_collection_select_with_multiple_option_appends_array_brackets_and_hidden_input @post = Post.new @post.author_name = "Babe" - expected = "<select id=\"post_author_name\" name=\"post[author_name][]\" multiple=\"multiple\"><option value=\"\"></option>\n<option value=\"<Abe>\"><Abe></option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>" + expected = "<input type=\"hidden\" name=\"post[author_name][]\" value=\"\"/><select id=\"post_author_name\" name=\"post[author_name][]\" multiple=\"multiple\"><option value=\"\"></option>\n<option value=\"<Abe>\"><Abe></option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>" # Should suffix default name with []. assert_dom_equal expected, collection_select("post", "author_name", dummy_posts, "author_name", "author_name", { :include_blank => true }, :multiple => true) diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index f95308b847..979251bfd1 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -9,7 +9,7 @@ class FormTagHelperTest < ActionView::TestCase @controller = BasicController.new end - def snowman(options = {}) + def hidden_fields(options = {}) method = options[:method] txt = %{<div style="margin:0;padding:0;display:inline">} @@ -34,7 +34,7 @@ class FormTagHelperTest < ActionView::TestCase end def whole_form(action = "http://www.example.com", options = {}) - out = form_text(action, options) + snowman(options) + out = form_text(action, options) + hidden_fields(options) if block_given? out << yield << "</form>" diff --git a/actionpack/test/template/html-scanner/document_test.rb b/actionpack/test/template/html-scanner/document_test.rb index ddfb351595..3db2fba783 100644 --- a/actionpack/test/template/html-scanner/document_test.rb +++ b/actionpack/test/template/html-scanner/document_test.rb @@ -123,7 +123,7 @@ HTML def test_parse_invalid_document assert_nothing_raised do - doc = HTML::Document.new("<html> + HTML::Document.new("<html> <table> <tr> <td style=\"color: #FFFFFF; height: 17px; onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" style=\"cursor:pointer; height: 17px;\"; nowrap onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" onmouseout=\"this.bgColor='#0066cc'; this.style.color='#FFFFFF'\" onmouseover=\"this.bgColor='#ffffff'; this.style.color='#0033cc'\">About Us</td> @@ -135,7 +135,7 @@ HTML def test_invalid_document_raises_exception_when_strict assert_raise RuntimeError do - doc = HTML::Document.new("<html> + HTML::Document.new("<html> <table> <tr> <td style=\"color: #FFFFFF; height: 17px; onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" style=\"cursor:pointer; height: 17px;\"; nowrap onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" onmouseout=\"this.bgColor='#0066cc'; this.style.color='#FFFFFF'\" onmouseover=\"this.bgColor='#ffffff'; this.style.color='#0033cc'\">About Us</td> diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb index 538e0e9874..dd8b7b7cd5 100644 --- a/actionpack/test/template/javascript_helper_test.rb +++ b/actionpack/test/template/javascript_helper_test.rb @@ -30,6 +30,15 @@ class JavaScriptHelperTest < ActionView::TestCase assert_equal %(dont <\\/close> tags), j(%(dont </close> tags)) end + def test_escape_javascript_with_safebuffer + given = %('quoted' "double-quoted" new-line:\n </closed>) + expect = %(\\'quoted\\' \\"double-quoted\\" new-line:\\n <\\/closed>) + assert_equal expect, escape_javascript(given) + assert_equal expect, escape_javascript(ActiveSupport::SafeBuffer.new(given)) + assert_instance_of String, escape_javascript(given) + assert_instance_of ActiveSupport::SafeBuffer, escape_javascript(ActiveSupport::SafeBuffer.new(given)) + end + def test_button_to_function assert_dom_equal %(<input type="button" onclick="alert('Hello world!');" value="Greeting" />), button_to_function("Greeting", "alert('Hello world!')") diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index 0104c20bc7..0e3475d98b 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -283,33 +283,40 @@ class NumberHelperTest < ActionView::TestCase assert number_to_human(1).html_safe? assert !number_to_human("<script></script>").html_safe? assert number_to_human("asdf".html_safe).html_safe? + assert number_to_human("1".html_safe).html_safe? assert number_to_human_size(1).html_safe? assert number_to_human_size(1000000).html_safe? assert !number_to_human_size("<script></script>").html_safe? assert number_to_human_size("asdf".html_safe).html_safe? + assert number_to_human_size("1".html_safe).html_safe? assert number_with_precision(1, :strip_insignificant_zeros => false).html_safe? assert number_with_precision(1, :strip_insignificant_zeros => true).html_safe? assert !number_with_precision("<script></script>").html_safe? assert number_with_precision("asdf".html_safe).html_safe? + assert number_with_precision("1".html_safe).html_safe? assert number_to_currency(1).html_safe? assert !number_to_currency("<script></script>").html_safe? assert number_to_currency("asdf".html_safe).html_safe? + assert number_to_currency("1".html_safe).html_safe? assert number_to_percentage(1).html_safe? assert !number_to_percentage("<script></script>").html_safe? assert number_to_percentage("asdf".html_safe).html_safe? + assert number_to_percentage("1".html_safe).html_safe? assert number_to_phone(1).html_safe? assert_equal "<script></script>", number_to_phone("<script></script>") assert number_to_phone("<script></script>").html_safe? assert number_to_phone("asdf".html_safe).html_safe? + assert number_to_phone("1".html_safe).html_safe? assert number_with_delimiter(1).html_safe? assert !number_with_delimiter("<script></script>").html_safe? assert number_with_delimiter("asdf".html_safe).html_safe? + assert number_with_delimiter("1".html_safe).html_safe? end def test_number_helpers_should_raise_error_if_invalid_when_specified diff --git a/actionpack/test/template/record_tag_helper_test.rb b/actionpack/test/template/record_tag_helper_test.rb index 74d7bba4fe..1ba14e8bc9 100644 --- a/actionpack/test/template/record_tag_helper_test.rb +++ b/actionpack/test/template/record_tag_helper_test.rb @@ -48,14 +48,12 @@ class RecordTagHelperTest < ActionView::TestCase end def test_block_works_with_content_tag_for_in_erb - __in_erb_template = '' expected = %(<tr class="post" id="post_45">#{@post.body}</tr>) actual = content_tag_for(:tr, @post) { concat @post.body } assert_dom_equal expected, actual end def test_div_for_in_erb - __in_erb_template = '' expected = %(<div class="post bar" id="post_45">#{@post.body}</div>) actual = div_for(@post, :class => "bar") { concat @post.body } assert_dom_equal expected, actual diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index 86d08a43a5..4187a0ac78 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -370,7 +370,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase def test_render_utf8_template_with_incompatible_external_encoding with_external_encoding Encoding::SHIFT_JIS do begin - result = @view.render(:file => "test/utf8.html.erb", :layouts => "layouts/yield") + @view.render(:file => "test/utf8.html.erb", :layouts => "layouts/yield") flunk 'Should have raised incompatible encoding error' rescue ActionView::Template::Error => error assert_match 'Your template was not saved as valid Shift_JIS', error.original_exception.message @@ -381,7 +381,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase def test_render_utf8_template_with_partial_with_incompatible_encoding with_external_encoding Encoding::SHIFT_JIS do begin - result = @view.render(:file => "test/utf8_magic_with_bare_partial.html.erb", :layouts => "layouts/yield") + @view.render(:file => "test/utf8_magic_with_bare_partial.html.erb", :layouts => "layouts/yield") flunk 'Should have raised incompatible encoding error' rescue ActionView::Template::Error => error assert_match 'Your template was not saved as valid Shift_JIS', error.original_exception.message diff --git a/actionpack/test/template/sprockets_helper_test.rb b/actionpack/test/template/sprockets_helper_test.rb index b1317d0a35..f11d1bba15 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", @@ -134,4 +197,16 @@ class SprocketsHelperTest < ActionView::TestCase assert_equal "<link href=\"/assets/style-d41d8cd98f00b204e9800998ecf8427e.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />\n<link href=\"/assets/extra-d41d8cd98f00b204e9800998ecf8427e.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />", stylesheet_link_tag("style", "extra") end + + test "alternate asset prefix" do + stubs(:asset_prefix).returns("/themes/test") + assert_equal "/themes/test/style-d41d8cd98f00b204e9800998ecf8427e.css", asset_path("style", "css") + end + + test "alternate asset environment" do + assets = Sprockets::Environment.new + assets.paths << FIXTURES.join("sprockets/alternate/stylesheets") + stubs(:asset_environment).returns(assets) + assert_equal "/assets/style-df0b97ad35a8e1f7f61097461f77c19a.css", asset_path("style", "css") + end end diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb index 81fb34b80f..b0ca7de0b6 100644 --- a/actionpack/test/template/template_test.rb +++ b/actionpack/test/template/template_test.rb @@ -153,7 +153,6 @@ class TestERBTemplate < ActiveSupport::TestCase def test_encoding_can_be_specified_with_magic_comment_in_erb with_external_encoding Encoding::UTF_8 do @template = new_template("<%# encoding: ISO-8859-1 %>hello \xFCmlat", :virtual_path => nil) - result = render assert_equal Encoding::UTF_8, render.encoding assert_equal "hello \u{fc}mlat", render end diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb index f463675a2e..a75f1bc981 100644 --- a/actionpack/test/template/test_case_test.rb +++ b/actionpack/test/template/test_case_test.rb @@ -45,6 +45,10 @@ module ActionView assert_same _view, view end + test "retrieve non existing config values" do + assert_equal nil, ActionView::Base.new.config.something_odd + end + test "works without testing a helper module" do assert_equal 'Eloy', render('developers/developer', :developer => stub(:name => 'Eloy')) end diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index 740f577a6e..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 @@ -291,7 +298,7 @@ class TextHelperTest < ActionView::TestCase end def test_cycle_class_with_no_arguments - assert_raise(ArgumentError) { value = Cycle.new() } + assert_raise(ArgumentError) { Cycle.new } end def test_cycle @@ -304,7 +311,7 @@ class TextHelperTest < ActionView::TestCase end def test_cycle_with_no_arguments - assert_raise(ArgumentError) { value = cycle() } + assert_raise(ArgumentError) { cycle } end def test_cycle_resets_with_new_values 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 4aa45c8bf0..a70c02a429 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -15,6 +15,7 @@ class UrlHelperTest < ActiveSupport::TestCase routes.draw do match "/" => "foo#bar" match "/other" => "foo#other" + match "/article/:id" => "foo#article", :as => :article end include routes.url_helpers @@ -244,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>", @@ -264,6 +272,13 @@ class UrlHelperTest < ActiveSupport::TestCase assert_equal '<a href="/">Example site</a>', out end + def test_link_tag_with_html_safe_string + assert_dom_equal( + "<a href=\"/article/Gerd_M%C3%BCller\">Gerd Müller</a>", + link_to("Gerd Müller", article_path("Gerd_Müller".html_safe)) + ) + end + def test_link_to_unless assert_equal "Showing", link_to_unless(true, "Showing", url_hash) diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG index b1ad315c46..c38349b95e 100644 --- a/activemodel/CHANGELOG +++ b/activemodel/CHANGELOG @@ -2,6 +2,9 @@ *Rails 3.1.0 (unreleased)* +* Alternate I18n namespace lookup is no longer supported. + Instead of "activerecord.models.admins.post", do "activerecord.models.admins/post" instead [José Valim] + * attr_accessible and friends now accepts :as as option to specify a role [Josh Kalderimis] * Add support for proc or lambda as an option for InclusionValidator, 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/errors.rb b/activemodel/lib/active_model/errors.rb index 0e2a2fd5e2..71ece15b71 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -248,7 +248,7 @@ module ActiveModel # # company = Company.create(:address => '123 First St.') # company.errors.full_messages # => - # ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"] + # ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"] def full_messages map { |attribute, message| if attribute == :base 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/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb index 0bfbf2aa06..4fbccd7419 100644 --- a/activemodel/lib/active_model/serializers/json.rb +++ b/activemodel/lib/active_model/serializers/json.rb @@ -22,46 +22,53 @@ module ActiveModel # of +as_json+. If true (the default) +as_json+ will emit a single root # node named after the object's type. For example: # - # konata = User.find(1) - # konata.as_json + # user = User.find(1) + # user.as_json # # => { "user": {"id": 1, "name": "Konata Izumi", "age": 16, # "created_at": "2006/08/01", "awesome": true} } # # ActiveRecord::Base.include_root_in_json = false - # konata.as_json + # user.as_json # # => {"id": 1, "name": "Konata Izumi", "age": 16, # "created_at": "2006/08/01", "awesome": true} # - # The remainder of the examples in this section assume +include_root_in_json+ - # is false. + # This behavior can also be achieved by setting the <tt>:root</tt> option to +false+ as in: + # + # user = User.find(1) + # user.as_json(root: false) + # # => {"id": 1, "name": "Konata Izumi", "age": 16, + # "created_at": "2006/08/01", "awesome": true} + # + # The remainder of the examples in this section assume include_root_in_json is set to + # <tt>false</tt>. # # Without any +options+, the returned JSON string will include all the model's # attributes. For example: # - # konata = User.find(1) - # konata.as_json + # user = User.find(1) + # user.as_json # # => {"id": 1, "name": "Konata Izumi", "age": 16, # "created_at": "2006/08/01", "awesome": true} # # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes # included, and work similar to the +attributes+ method. For example: # - # konata.as_json(:only => [ :id, :name ]) + # user.as_json(:only => [ :id, :name ]) # # => {"id": 1, "name": "Konata Izumi"} # - # konata.as_json(:except => [ :id, :created_at, :age ]) + # user.as_json(:except => [ :id, :created_at, :age ]) # # => {"name": "Konata Izumi", "awesome": true} # # To include the result of some method calls on the model use <tt>:methods</tt>: # - # konata.as_json(:methods => :permalink) + # user.as_json(:methods => :permalink) # # => {"id": 1, "name": "Konata Izumi", "age": 16, # "created_at": "2006/08/01", "awesome": true, # "permalink": "1-konata-izumi"} # # To include associations use <tt>:include</tt>: # - # konata.as_json(:include => :posts) + # user.as_json(:include => :posts) # # => {"id": 1, "name": "Konata Izumi", "age": 16, # "created_at": "2006/08/01", "awesome": true, # "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"}, @@ -69,7 +76,7 @@ module ActiveModel # # Second level and higher order associations work as well: # - # konata.as_json(:include => { :posts => { + # user.as_json(:include => { :posts => { # :include => { :comments => { # :only => :body } }, # :only => :title } }) @@ -83,7 +90,12 @@ module ActiveModel def as_json(options = nil) hash = serializable_hash(options) - if include_root_in_json + include_root = include_root_in_json + if options.try(:key?, :root) + include_root = options[:root] + end + + if include_root custom_root = options && options[:root] hash = { custom_root || self.class.model_name.element => hash } end @@ -91,9 +103,9 @@ module ActiveModel hash end - def from_json(json) + def from_json(json, include_root=include_root_in_json) hash = ActiveSupport::JSON.decode(json) - hash = hash.values.first if include_root_in_json + hash = hash.values.first if include_root self.attributes = hash self end diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb index f6aae24324..9812af43d6 100644 --- a/activemodel/lib/active_model/serializers/xml.rb +++ b/activemodel/lib/active_model/serializers/xml.rb @@ -139,8 +139,8 @@ module ActiveModel # Without any +options+, the returned XML string will include all the model's # attributes. For example: # - # konata = User.find(1) - # konata.to_xml + # user = User.find(1) + # user.to_xml # # <?xml version="1.0" encoding="UTF-8"?> # <user> diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb index 48d14978fa..144e73904e 100644 --- a/activemodel/lib/active_model/validations/length.rb +++ b/activemodel/lib/active_model/validations/length.rb @@ -62,14 +62,14 @@ module ActiveModel # Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time: # # class Person < ActiveRecord::Base - # validates_length_of :first_name, :maximum=>30 - # validates_length_of :last_name, :maximum=>30, :message=>"less than 30 if you don't mind" + # validates_length_of :first_name, :maximum => 30 + # validates_length_of :last_name, :maximum => 30, :message => "less than 30 if you don't mind" # validates_length_of :fax, :in => 7..32, :allow_nil => true # validates_length_of :phone, :in => 7..32, :allow_blank => true # validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name" # validates_length_of :zip_code, :minimum => 5, :too_short => "please enter at least 5 characters" # validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with 4 characters... don't play me." - # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least 100 words."), :tokenizer => lambda {|str| str.scan(/\w+/) } + # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least 100 words.", :tokenizer => lambda { |str| str.scan(/\w+/) } # end # # Configuration options: diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb index 09684ac4df..dbda55ca7c 100644 --- a/activemodel/lib/active_model/version.rb +++ b/activemodel/lib/active_model/version.rb @@ -1,9 +1,9 @@ module ActiveModel module VERSION #:nodoc: MAJOR = 3 - MINOR = 1 + MINOR = 2 TINY = 0 - PRE = "rc1" + PRE = "beta" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb index 022c6716bd..9840e3364c 100644 --- a/activemodel/test/cases/attribute_methods_test.rb +++ b/activemodel/test/cases/attribute_methods_test.rb @@ -78,7 +78,6 @@ class AttributeMethodsTest < ActiveModel::TestCase test '#define_attribute_method generates attribute method with invalid identifier characters' do ModelWithWeirdNamesAttributes.define_attribute_method(:'a?b') - ModelWithWeirdNamesAttributes.define_attribute_method(:'a?b') assert_respond_to ModelWithWeirdNamesAttributes.new, :'a?b' assert_equal "value of a?b", ModelWithWeirdNamesAttributes.new.send('a?b') 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/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb index 500a5c575f..5e1e7d897a 100644 --- a/activemodel/test/cases/serializers/json_serialization_test.rb +++ b/activemodel/test/cases/serializers/json_serialization_test.rb @@ -8,6 +8,12 @@ class Contact include ActiveModel::Serializers::JSON include ActiveModel::Validations + def attributes=(hash) + hash.each do |k, v| + instance_variable_set("@#{k}", v) + end + end + def attributes instance_values end unless method_defined?(:attributes) @@ -34,7 +40,7 @@ class JsonSerializationTest < ActiveModel::TestCase assert_match %r{"preferences":\{"shows":"anime"\}}, json end - test "should not include root in json" do + test "should not include root in json (class method)" do begin Contact.include_root_in_json = false json = @contact.to_json @@ -50,6 +56,13 @@ class JsonSerializationTest < ActiveModel::TestCase end end + test "should not include root in json (option)" do + + json = @contact.to_json(:root => false) + + assert_no_match %r{^\{"contact":\{}, json + end + test "should include custom root in json" do json = @contact.to_json(:root => 'json_contact') @@ -135,6 +148,44 @@ class JsonSerializationTest < ActiveModel::TestCase end end + test "from_json should set the object's attributes" do + json = @contact.to_json + result = Contact.new.from_json(json) + + assert_equal result.name, @contact.name + assert_equal result.age, @contact.age + assert_equal Time.parse(result.created_at), @contact.created_at + assert_equal result.awesome, @contact.awesome + assert_equal result.preferences, @contact.preferences + end + + test "from_json should work without a root (method parameter)" do + json = @contact.to_json(:root => false) + result = Contact.new.from_json(json, false) + + assert_equal result.name, @contact.name + assert_equal result.age, @contact.age + assert_equal Time.parse(result.created_at), @contact.created_at + assert_equal result.awesome, @contact.awesome + assert_equal result.preferences, @contact.preferences + end + + test "from_json should work without a root (class attribute)" do + begin + Contact.include_root_in_json = false + json = @contact.to_json + result = Contact.new.from_json(json) + + assert_equal result.name, @contact.name + assert_equal result.age, @contact.age + assert_equal Time.parse(result.created_at), @contact.created_at + assert_equal result.awesome, @contact.awesome + assert_equal result.preferences, @contact.preferences + ensure + Contact.include_root_in_json = true + end + end + test "custom as_json should be honored when generating json" do def @contact.as_json(options); { :name => name, :created_at => created_at }; end json = @contact.to_json 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/CHANGELOG b/activerecord/CHANGELOG index b05d3970c7..4113a16c12 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,17 @@ *Rails 3.1.0 (unreleased)* +* ActiveRecord::MacroReflection::AssociationReflection#build_record has a new method signature. + + Before: def build_association(*options) + After: def build_association(*options, &block) + + Users who are redefining this method to extend functionality should ensure that the block is + passed through to ActiveRecord::Base#new. + + This change is necessary to fix https://github.com/rails/rails/issues/1842. + + [Jon Leighton] + * AR#pluralize_table_names can be used to singularize/pluralize table name of an individual model: class User < ActiveRecord::Base @@ -240,7 +252,6 @@ def up create_table :posts do |t| t.belongs_to :user - t.timestamps end diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc index 0d00749901..822276589b 100644 --- a/activerecord/README.rdoc +++ b/activerecord/README.rdoc @@ -3,7 +3,7 @@ Active Record connects classes to relational database tables to establish an almost zero-configuration persistence layer for applications. The library provides a base class that, when subclassed, sets up a mapping between the new -class and an existing table in the database. In context of an application, +class and an existing table in the database. In the context of an application, these classes are commonly referred to as *models*. Models can also be connected to other models; this is done by defining *associations*. @@ -70,7 +70,7 @@ A short rundown of some of the major features: {Learn more}[link:classes/ActiveRecord/Validations.html] -* Callbacks available for the entire life cycle (instantiation, saving, destroying, validating, etc.) +* Callbacks available for the entire life cycle (instantiation, saving, destroying, validating, etc.). class Person < ActiveRecord::Base before_destroy :invalidate_payment_plan @@ -80,7 +80,7 @@ A short rundown of some of the major features: {Learn more}[link:classes/ActiveRecord/Callbacks.html] -* Observers that react to changes in a model +* Observers that react to changes in a model. class CommentObserver < ActiveRecord::Observer def after_create(comment) # is called just after Comment#save @@ -91,7 +91,7 @@ A short rundown of some of the major features: {Learn more}[link:classes/ActiveRecord/Observer.html] -* Inheritance hierarchies +* Inheritance hierarchies. class Company < ActiveRecord::Base; end class Firm < Company; end @@ -101,7 +101,7 @@ A short rundown of some of the major features: {Learn more}[link:classes/ActiveRecord/Base.html] -* Transactions +* Transactions. # Database transaction Account.transaction do @@ -112,7 +112,7 @@ A short rundown of some of the major features: {Learn more}[link:classes/ActiveRecord/Transactions/ClassMethods.html] -* Reflections on columns, associations, and aggregations +* Reflections on columns, associations, and aggregations. reflection = Firm.reflect_on_association(:clients) reflection.klass # => Client (class) @@ -121,7 +121,7 @@ A short rundown of some of the major features: {Learn more}[link:classes/ActiveRecord/Reflection/ClassMethods.html] -* Database abstraction through simple adapters +* Database abstraction through simple adapters. # connect to SQLite3 ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => "dbfile.sqlite3") @@ -141,13 +141,13 @@ A short rundown of some of the major features: SQLite3[link:classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html]. -* Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc] +* Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc]. ActiveRecord::Base.logger = Logger.new(STDOUT) ActiveRecord::Base.logger = Log4r::Logger.new("Application Log") -* Database agnostic schema management with Migrations +* Database agnostic schema management with Migrations. class AddSystemSettings < ActiveRecord::Migration def self.up diff --git a/activerecord/RUNNING_UNIT_TESTS b/activerecord/RUNNING_UNIT_TESTS index bc4faa488c..6a2e23b01f 100644 --- a/activerecord/RUNNING_UNIT_TESTS +++ b/activerecord/RUNNING_UNIT_TESTS @@ -9,17 +9,31 @@ You can build postgres and mysql databases using the build_postgresql and build_ You can run a particular test file from the command line, e.g. - $ ruby test/cases/base_test.rb + $ ruby -Itest test/cases/base_test.rb To run a specific test: - $ ruby test/cases/base_test.rb -n test_something_works + $ ruby -Itest test/cases/base_test.rb -n test_something_works You can run with a database other than the default you set in test/config.yml, using the ARCONN environment variable: - $ ARCONN=postgresql ruby test/cases/base_test.rb + $ ARCONN=postgresql ruby -Itest test/cases/base_test.rb You can run all the tests for a given database via rake: - $ rake test_postgresql + $ rake test_mysql + +The 'rake test' task will run all the tests for mysql, mysql2, sqlite3 and postgresql. + +== Identity Map + +By default the tests run with the Identity Map turned off. But all tests should pass whether or +not the identity map is on or off. You can turn it on using the IM env variable: + + $ IM=true ruby -Itest test/case/base_test.rb + +== Config file + +By default, the config file is expected to be at the path test/config.yml. You can specify a +custom location with the ARCONFIG environment variable. diff --git a/activerecord/Rakefile b/activerecord/Rakefile index d5863ab14d..d769a73dba 100755 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -4,8 +4,7 @@ require 'rake/packagetask' require 'rubygems/package_task' require File.expand_path(File.dirname(__FILE__)) + "/test/config" - -MYSQL_DB_USER = 'rails' +require File.expand_path(File.dirname(__FILE__)) + "/test/support/config" def run_without_aborting(*tasks) errors = [] @@ -44,13 +43,13 @@ end %w( mysql mysql2 postgresql sqlite3 sqlite3_mem firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter| Rake::TestTask.new("test_#{adapter}") { |t| adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] + t.libs << 'test' t.test_files = (Dir.glob( "test/cases/**/*_test.rb" ).reject { |x| x =~ /\/adapters\// } + Dir.glob("test/cases/adapters/#{adapter_short}/**/*_test.rb")).sort t.verbose = true t.warning = true - t.ruby_opts = ["-r#{File.expand_path('../', __FILE__)}/test/connections/#{adapter}"] } task "isolated_test_#{adapter}" do @@ -60,14 +59,21 @@ end (Dir["test/cases/**/*_test.rb"].reject { |x| x =~ /\/adapters\// } + Dir["test/cases/adapters/#{adapter_short}/**/*_test.rb"]).all? do |file| - sh(ruby, "-Itest", "-r#{File.expand_path('../', __FILE__)}/test/connections/#{adapter}", file) + sh(ruby, "-Itest", file) end or raise "Failures" end namespace adapter do task :test => "test_#{adapter}" task :isolated_test => "isolated_test_#{adapter}" + + # Set the connection environment for the adapter + task(:env) { ENV['ARCONN'] = adapter } end + + # Make sure the adapter test evaluates the env setting task + task "test_#{adapter}" => "#{adapter}:env" + task "isolated_test_#{adapter}" => "#{adapter}:env" end rule '.sqlite3' do |t| @@ -82,14 +88,16 @@ task :test_sqlite3 => [ namespace :mysql do desc 'Build the MySQL test databases' task :build_databases do - %x( mysql --user=#{MYSQL_DB_USER} -e "create DATABASE activerecord_unittest DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") - %x( mysql --user=#{MYSQL_DB_USER} -e "create DATABASE activerecord_unittest2 DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") + config = ARTest.config['connections']['mysql'] + %x( mysql --user=#{config['arunit']['username']} -e "create DATABASE #{config['arunit']['database']} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") + %x( mysql --user=#{config['arunit2']['username']} -e "create DATABASE #{config['arunit2']['database']} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") end desc 'Drop the MySQL test databases' task :drop_databases do - %x( mysqladmin --user=#{MYSQL_DB_USER} -f drop activerecord_unittest ) - %x( mysqladmin --user=#{MYSQL_DB_USER} -f drop activerecord_unittest2 ) + config = ARTest.config['connections']['mysql'] + %x( mysqladmin --user=#{config['arunit']['username']} -f drop #{config['arunit']['database']} ) + %x( mysqladmin --user=#{config['arunit2']['username']} -f drop #{config['arunit2']['database']} ) end desc 'Rebuild the MySQL test databases' @@ -104,14 +112,16 @@ task :rebuild_mysql_databases => 'mysql:rebuild_databases' namespace :postgresql do desc 'Build the PostgreSQL test databases' task :build_databases do - %x( createdb -E UTF8 activerecord_unittest ) - %x( createdb -E UTF8 activerecord_unittest2 ) + config = ARTest.config['connections']['postgresql'] + %x( createdb -E UTF8 #{config['arunit']['database']} ) + %x( createdb -E UTF8 #{config['arunit2']['database']} ) end desc 'Drop the PostgreSQL test databases' task :drop_databases do - %x( dropdb activerecord_unittest ) - %x( dropdb activerecord_unittest2 ) + config = ARTest.config['connections']['postgresql'] + %x( dropdb #{config['arunit']['database']} ) + %x( dropdb #{config['arunit2']['database']} ) end desc 'Rebuild the PostgreSQL test databases' @@ -150,8 +160,9 @@ namespace :frontbase do DISCONNECT ALL; ) end - create_activerecord_unittest = build_frontbase_database['activerecord_unittest', File.join(SCHEMA_ROOT, 'frontbase.sql')] - create_activerecord_unittest2 = build_frontbase_database['activerecord_unittest2', File.join(SCHEMA_ROOT, 'frontbase2.sql')] + config = ARTest.config['connections']['frontbase'] + create_activerecord_unittest = build_frontbase_database[config['arunit']['database'], File.join(SCHEMA_ROOT, 'frontbase.sql')] + create_activerecord_unittest2 = build_frontbase_database[config['arunit2']['database'], File.join(SCHEMA_ROOT, 'frontbase2.sql')] execute_frontbase_sql = Proc.new do |sql| system(<<-SHELL) /Library/FrontBase/bin/sql92 <<-SQL diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index b7e23faa09..8ac4d9a225 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('tzinfo', '~> 0.3.27') + s.add_dependency('arel', '~> 2.1.3') + s.add_dependency('tzinfo', '~> 0.3.29') end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 3acfa5f729..029d7a9b15 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -191,7 +191,7 @@ module ActiveRecord # * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt> # * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt> # * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt> - # <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.find(:all, options),</tt> + # <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.all(options),</tt> # <tt>Project#milestones.build, Project#milestones.create</tt> # * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt> # <tt>Project#categories.delete(category1)</tt> @@ -420,7 +420,7 @@ module ActiveRecord # end # end # - # person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson") + # person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson") # person.first_name # => "David" # person.last_name # => "Heinemeier Hansson" # @@ -452,12 +452,12 @@ module ActiveRecord # end # # Some extensions can only be made to work with knowledge of the association's internals. - # Extensions can access relevant state using the following methods (where 'items' is the + # Extensions can access relevant state using the following methods (where +items+ is the # name of the association): # - # * +record.association(:items).owner+ - Returns the object the association is part of. - # * +record.association(:items).reflection+ - Returns the reflection object that describes the association. - # * +record.association(:items).target+ - Returns the associated object for +belongs_to+ and +has_one+, or + # * <tt>record.association(:items).owner</tt> - Returns the object the association is part of. + # * <tt>record.association(:items).reflection</tt> - Returns the reflection object that describes the association. + # * <tt>record.association(:items).target</tt> - Returns the associated object for +belongs_to+ and +has_one+, or # the collection of associated objects for +has_many+ and +has_and_belongs_to_many+. # # === Association Join Models @@ -477,7 +477,7 @@ module ActiveRecord # belongs_to :book # end # - # @author = Author.find :first + # @author = Author.first # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to # @author.books # selects all books by using the Authorship join model # @@ -497,7 +497,7 @@ module ActiveRecord # belongs_to :client # end # - # @firm = Firm.find :first + # @firm = Firm.first # @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm # @firm.invoices # selects all invoices by going through the Client join model # @@ -669,7 +669,7 @@ module ActiveRecord # To iterate over these one hundred posts, we'll generate 201 database queries. Let's # first just optimize it for retrieving the author: # - # Post.find(:all, :include => :author).each do |post| + # Post.includes(:author).each do |post| # # This references the name of the +belongs_to+ association that also used the <tt>:author</tt> # symbol. After loading the posts, find will collect the +author_id+ from each one and load @@ -678,7 +678,7 @@ module ActiveRecord # # We can improve upon the situation further by referencing both associations in the finder with: # - # Post.find(:all, :include => [ :author, :comments ]).each do |post| + # Post.includes(:author, :comments).each do |post| # # This will load all comments with a single query. This reduces the total number of queries # to 3. More generally the number of queries will be 1 plus the number of associations @@ -686,7 +686,7 @@ module ActiveRecord # # To include a deep hierarchy of associations, use a hash: # - # Post.find(:all, :include => [ :author, { :comments => { :author => :gravatar } } ]).each do |post| + # Post.includes(:author, {:comments => {:author => :gravatar}}).each do |post| # # That'll grab not only all the comments but all their authors and gravatar pictures. # You can mix and match symbols, arrays and hashes in any combination to describe the @@ -714,13 +714,13 @@ module ActiveRecord # <tt>:order => "author.name DESC"</tt> will work but <tt>:order => "name DESC"</tt> will not. # # If you do want eager load only some members of an association it is usually more natural - # to <tt>:include</tt> an association which has conditions defined on it: + # to include an association which has conditions defined on it: # # class Post < ActiveRecord::Base # has_many :approved_comments, :class_name => 'Comment', :conditions => ['approved = ?', true] # end # - # Post.find(:all, :include => :approved_comments) + # Post.includes(:approved_comments) # # This will load posts and eager load the +approved_comments+ association, which contains # only those comments that have been approved. @@ -732,7 +732,7 @@ module ActiveRecord # has_many :most_recent_comments, :class_name => 'Comment', :order => 'id DESC', :limit => 10 # end # - # Picture.find(:first, :include => :most_recent_comments).most_recent_comments # => returns all associated comments. + # Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments. # # When eager loaded, conditions are interpolated in the context of the model class, not # the model instance. Conditions are lazily interpolated before the actual model exists. @@ -745,7 +745,7 @@ module ActiveRecord # # A call that tries to eager load the addressable model # - # Address.find(:all, :include => :addressable) + # Address.includes(:addressable) # # This will execute one query to load the addresses and load the addressables with one # query per addressable type. @@ -763,43 +763,43 @@ module ActiveRecord # second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>. # Indexes are appended for any more successive uses of the table name. # - # Post.find :all, :joins => :comments + # Post.joins(:comments) # # => SELECT ... FROM posts INNER JOIN comments ON ... - # Post.find :all, :joins => :special_comments # STI + # Post.joins(:special_comments) # STI # # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment' - # Post.find :all, :joins => [:comments, :special_comments] # special_comments is the reflection name, posts is the parent table name + # Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name # # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts # # Acts as tree example: # - # TreeMixin.find :all, :joins => :children + # TreeMixin.joins(:children) # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... - # TreeMixin.find :all, :joins => {:children => :parent} + # TreeMixin.joins(:children => :parent) # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... # INNER JOIN parents_mixins ... - # TreeMixin.find :all, :joins => {:children => {:parent => :children}} + # TreeMixin.joins(:children => {:parent => :children}) # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... # INNER JOIN parents_mixins ... # INNER JOIN mixins childrens_mixins_2 # # Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix: # - # Post.find :all, :joins => :categories + # Post.joins(:categories) # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... - # Post.find :all, :joins => {:categories => :posts} + # Post.joins(:categories => :posts) # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories - # Post.find :all, :joins => {:categories => {:posts => :categories}} + # Post.joins(:categories => {:posts => :categories}) # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories # INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2 # - # If you wish to specify your own custom joins using a <tt>:joins</tt> option, those table + # If you wish to specify your own custom joins using <tt>joins</tt> method, those table # names will take precedence over the eager associations: # - # Post.find :all, :joins => :comments, :joins => "inner join comments ..." + # Post.joins(:comments).joins("inner join comments ...") # # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ... - # Post.find :all, :joins => [:comments, :special_comments], :joins => "inner join comments ..." + # Post.joins(:comments, :special_comments).joins("inner join comments ...") # # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ... # INNER JOIN comments special_comments_posts ... # INNER JOIN comments ... @@ -1031,7 +1031,7 @@ module ActiveRecord # === Example # # Example: A Firm class declares <tt>has_many :clients</tt>, which will add: - # * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => ["firm_id = ?", id]</tt>) + # * <tt>Firm#clients</tt> (similar to <tt>Clients.all :conditions => ["firm_id = ?", id]</tt>) # * <tt>Firm#clients<<</tt> # * <tt>Firm#clients.delete</tt> # * <tt>Firm#clients=</tt> @@ -1195,7 +1195,7 @@ module ActiveRecord # === Example # # An Account class declares <tt>has_one :beneficiary</tt>, which will add: - # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.find(:first, :conditions => "account_id = #{id}")</tt>) + # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.first(:conditions => "account_id = #{id}")</tt>) # * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>) # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>) # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>) diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index bd98cf2f46..92ed844a2e 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -9,7 +9,7 @@ module ActiveRecord # table_joins is an array of arel joins which might conflict with the aliases we assign here def initialize(table_joins = []) - @aliases = Hash.new + @aliases = Hash.new { |h,k| h[k] = initial_count_for(k) } @table_joins = table_joins end @@ -26,8 +26,6 @@ module ActiveRecord def aliased_name_for(table_name, aliased_name = nil) aliased_name ||= table_name - initialize_count_for(table_name) if aliases[table_name].nil? - if aliases[table_name].zero? # If it's zero, we can have our table_name aliases[table_name] = 1 @@ -36,8 +34,6 @@ module ActiveRecord # Otherwise, we need to use an alias aliased_name = connection.table_alias_for(aliased_name) - initialize_count_for(aliased_name) if aliases[aliased_name].nil? - # Update the count aliases[aliased_name] += 1 @@ -49,32 +45,24 @@ module ActiveRecord end end - def pluralize(table_name, base) - base.pluralize_table_names ? table_name.to_s.pluralize : table_name.to_s - end - private - def initialize_count_for(name) - aliases[name] = 0 - - unless Arel::Table === table_joins - # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase - quoted_name = connection.quote_table_name(name).downcase + def initial_count_for(name) + return 0 if Arel::Table === table_joins - aliases[name] += table_joins.map { |join| - # Table names + table aliases - join.left.downcase.scan( - /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/ - ).size - }.sum - end + # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase + quoted_name = connection.quote_table_name(name).downcase - aliases[name] + table_joins.map { |join| + # Table names + table aliases + join.left.downcase.scan( + /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/ + ).size + }.sum end def truncate(name) - name[0..connection.table_alias_length-3] + name.slice(0, connection.table_alias_length - 2) end def connection diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 687b668634..bb519c5703 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -30,7 +30,7 @@ module ActiveRecord @updated = false reset - construct_scope + reset_scope end # Returns the name of the table of the related class: @@ -51,7 +51,7 @@ module ActiveRecord # Reloads the \target and returns +self+ on success. def reload reset - construct_scope + reset_scope load_target self unless target.nil? end @@ -84,21 +84,25 @@ module ActiveRecord end def scoped - target_scope.merge(@association_scope) + target_scope.merge(association_scope) end - # Construct the scope for this association. + # The scope for this association. # # Note that the association_scope is merged into the target_scope only when the # scoped method is called. This is because at that point the call may be surrounded # by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which # actually gets built. - def construct_scope + def association_scope if klass - @association_scope = AssociationScope.new(self).scope + @association_scope ||= AssociationScope.new(self).scope end end + def reset_scope + @association_scope = nil + end + # Set the inverse association, if possible def set_inverse_instance(record) if record && invertible_for?(record) @@ -141,7 +145,7 @@ module ActiveRecord @target ||= find_target end end - loaded! + loaded! unless loaded? target rescue ActiveRecord::RecordNotFound reset @@ -177,9 +181,7 @@ module ActiveRecord # Sets the owner attributes on the given record def set_owner_attributes(record) - if owner.persisted? - creation_attributes.each { |key, value| record[key] = value } - end + creation_attributes.each { |key, value| record[key] = value } end # Should be true if there is a foreign key present on the owner which @@ -226,6 +228,15 @@ module ActiveRecord def association_class @reflection.klass end + + def build_record(attributes, options) + reflection.build_association(attributes, options) do |record| + record.assign_attributes( + create_scope.except(*record.changed), + :without_protection => true + ) + end + end end end end diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index c263edd2c6..adb6af7165 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -37,7 +37,7 @@ module ActiveRecord # Checks whether record is different to the current target, without loading it def different_target?(record) record.nil? && owner[reflection.foreign_key] || - record.id != owner[reflection.foreign_key] + record && record.id != owner[reflection.foreign_key] end def replace_keys(record) diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb index d7632b2ea9..30fc44b4c2 100644 --- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb @@ -7,24 +7,22 @@ module ActiveRecord::Associations::Builder def build reflection = super check_validity(reflection) - define_after_destroy_method + define_destroy_hook reflection end private - def define_after_destroy_method + def define_destroy_hook name = self.name - model.send(:class_eval, <<-eoruby, __FILE__, __LINE__ + 1) - def #{after_destroy_method_name} - association(#{name.to_sym.inspect}).delete_all - end - eoruby - model.after_destroy after_destroy_method_name - end - - def after_destroy_method_name - "has_and_belongs_to_many_after_destroy_for_#{name}" + model.send(:include, Module.new { + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def destroy_associations + association(#{name.to_sym.inspect}).delete_all + super + end + RUBY + }) end # TODO: These checks should probably be moved into the Reflection, and we should not be diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 7e1a41e84d..3e68f973e7 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -50,7 +50,7 @@ module ActiveRecord else column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}" - scoped.select(column).except(:includes).map! do |record| + scoped.select(column).map! do |record| record.send(reflection.association_primary_key) end end @@ -78,10 +78,14 @@ module ActiveRecord end def find(*args) - if options[:finder_sql] - find_by_scan(*args) + if block_given? + load_target.find(*args) { |*block_args| yield(*block_args) } else - scoped.find(*args) + if options[:finder_sql] + find_by_scan(*args) + else + scoped.find(*args) + end end end @@ -114,19 +118,13 @@ module ActiveRecord # Add +records+ to this association. Returns +self+ so method calls may be chained. # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically. def concat(*records) - result = true load_target if owner.new_record? - transaction do - records.flatten.each do |record| - raise_on_type_mismatch(record) - add_to_target(record) do |r| - result &&= insert_record(record) unless owner.new_record? - end - end + if owner.new_record? + concat_records(records) + else + transaction { concat_records(records) } end - - result && records end # Starts a transaction in the association class's database connection. @@ -295,14 +293,10 @@ module ActiveRecord other_array.each { |val| raise_on_type_mismatch(val) } original_target = load_target.dup - transaction do - delete(target - other_array) - - unless concat(other_array - target) - @target = original_target - raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \ - "new records could not be saved." - end + if owner.new_record? + replace_records(other_array, original_target) + else + transaction { replace_records(other_array, original_target) } end end @@ -433,25 +427,25 @@ module ActiveRecord scoped.scope_for_create.stringify_keys end - def build_record(attributes, options) - record = reflection.build_association(attributes, options) - record.assign_attributes(create_scope.except(*record.changed), :without_protection => true) - record - end - def delete_or_destroy(records, method) records = records.flatten records.each { |record| raise_on_type_mismatch(record) } existing_records = records.reject { |r| r.new_record? } - transaction do - records.each { |record| callback(:before_remove, record) } + if existing_records.empty? + remove_records(existing_records, records, method) + else + transaction { remove_records(existing_records, records, method) } + end + end - delete_records(existing_records, method) if existing_records.any? - records.each { |record| target.delete(record) } + def remove_records(existing_records, records, method) + records.each { |record| callback(:before_remove, record) } - records.each { |record| callback(:after_remove, record) } - end + delete_records(existing_records, method) if existing_records.any? + records.each { |record| target.delete(record) } + + records.each { |record| callback(:after_remove, record) } end # Delete the given records from the association, using one of the methods :destroy, @@ -460,6 +454,29 @@ module ActiveRecord raise NotImplementedError end + def replace_records(new_target, original_target) + delete(target - new_target) + + unless concat(new_target - target) + @target = original_target + raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \ + "new records could not be saved." + end + end + + def concat_records(records) + result = true + + records.flatten.each do |record| + raise_on_type_mismatch(record) + add_to_target(record) do |r| + result &&= insert_record(record) unless owner.new_record? + end + end + + result && records + end + def callback(method, record) callbacks_for(method).each do |callback| case callback diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 81b4a26b04..827dfb7ccb 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -12,7 +12,7 @@ module ActiveRecord # has_many :posts # end # - # blog = Blog.find(:first) + # blog = Blog.first # # the association proxy in <tt>blog.posts</tt> has the object in +blog+ as # <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and @@ -58,16 +58,16 @@ module ActiveRecord alias_method :new, :build - def respond_to?(*args) + def respond_to?(name, include_private = false) super || - (load_target && target.respond_to?(*args)) || - @association.klass.respond_to?(*args) + (load_target && target.respond_to?(name, include_private)) || + @association.klass.respond_to?(name, include_private) end def method_missing(method, *args, &block) match = DynamicFinderMatch.match(method) if match && match.instantiator? - record = send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r| + send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r| @association.send :set_owner_attributes, r @association.send :add_to_target, r yield(r) if block_given? diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 2f3a6e71f1..2131edbc20 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -14,12 +14,12 @@ module ActiveRecord end if record - set_inverse_instance(record) set_owner_attributes(record) + set_inverse_instance(record) if owner.persisted? && save && !record.save nullify_owner_attributes(record) - set_owner_attributes(target) + set_owner_attributes(target) if target raise RecordNotSaved, "Failed to save the new associated #{reflection.name}." end end diff --git a/activerecord/lib/active_record/associations/join_helper.rb b/activerecord/lib/active_record/associations/join_helper.rb index 87e33891a5..f83138195c 100644 --- a/activerecord/lib/active_record/associations/join_helper.rb +++ b/activerecord/lib/active_record/associations/join_helper.rb @@ -32,8 +32,7 @@ module ActiveRecord end def table_alias_for(reflection, join = false) - name = alias_tracker.pluralize(reflection.name, reflection.active_record) - name << "_#{alias_suffix}" + name = "#{reflection.plural_name}_#{alias_suffix}" name << "_join" if join name end diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 7256dd5288..779f8164cc 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -68,7 +68,8 @@ module ActiveRecord private def associated_records_by_owner - owner_keys = owners.map { |owner| owner[owner_key_name] }.compact.uniq + owners_map = owners_by_key + owner_keys = owners_map.keys.compact if klass.nil? || owner_keys.empty? records = [] @@ -84,7 +85,7 @@ module ActiveRecord records.each do |record| owner_key = record[association_key_name].to_s - owners_by_key[owner_key].each do |owner| + owners_map[owner_key].each do |owner| records_by_owner[owner] << record end end diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index ce1f2a5543..a1a921bcb4 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -18,16 +18,15 @@ module ActiveRecord end def create(attributes = {}, options = {}, &block) - build(attributes, options, &block).tap { |record| record.save } + create_record(attributes, options, &block) end def create!(attributes = {}, options = {}, &block) - build(attributes, options, &block).tap { |record| record.save! } + create_record(attributes, options, true, &block) end def build(attributes = {}, options = {}) - record = reflection.build_association(attributes, options) - record.assign_attributes(create_scope.except(*record.changed), :without_protection => true) + record = build_record(attributes, options) yield(record) if block_given? set_new_record(record) record @@ -51,6 +50,15 @@ module ActiveRecord def set_new_record(record) replace(record) end + + def create_record(attributes, options, raise_error = false) + record = build_record(attributes, options) + yield(record) if block_given? + saved = record.save + set_new_record(record) + raise RecordInvalid.new(record) if !saved && raise_error + record + end end end end diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 5833c65893..d0687ed0b6 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -61,7 +61,7 @@ module ActiveRecord end end - def respond_to?(*args) + def respond_to?(name, include_private = false) self.class.define_attribute_methods unless self.class.attribute_methods_generated? super end diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 5f06452247..8bd898d126 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -7,7 +7,7 @@ module ActiveRecord # the record is not persisted? or has just been destroyed. def to_key key = send(self.class.primary_key) - [key] if key + persisted? && key ? [key] : nil end module ClassMethods @@ -48,7 +48,7 @@ module ActiveRecord end attr_accessor :original_primary_key - + # Attribute writer for the primary key column def primary_key=(value) @quoted_primary_key = nil diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 7d4182291a..9a50a20fbc 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -99,8 +99,9 @@ module ActiveRecord # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example, # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). def read_attribute(attr_name) - if respond_to? "_#{attr_name}" - send "_#{attr_name}" if @attributes.has_key?(attr_name.to_s) + method = "_#{attr_name}" + if respond_to? method + send method if @attributes.has_key?(attr_name.to_s) else _read_attribute attr_name end diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 036d514a03..085fdba639 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -347,7 +347,7 @@ module ActiveRecord end # reconstruct the scope now that we know the owner's id - association.send(:construct_scope) if association.respond_to?(:construct_scope) + association.send(:reset_scope) if association.respond_to?(:reset_scope) end end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 08cc282d09..eb4a16ecf5 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 @@ -659,8 +655,8 @@ module ActiveRecord #:nodoc: def set_table_name(value = nil, &block) @quoted_table_name = nil define_attr_method :table_name, value, &block + @arel_table = nil - @arel_table = Arel::Table.new(table_name, arel_engine) @relation = Relation.new(self, arel_table) end alias :table_name= :set_table_name @@ -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 } @@ -892,7 +894,7 @@ module ActiveRecord #:nodoc: end def arel_table - Arel::Table.new(table_name, arel_engine) + @arel_table ||= Arel::Table.new(table_name, arel_engine) end def arel_engine @@ -1056,7 +1058,7 @@ module ActiveRecord #:nodoc: if match.finder? options = arguments.extract_options! relation = options.any? ? scoped(options) : scoped - relation.send :find_by_attributes, match, attribute_names, *arguments + relation.send :find_by_attributes, match, attribute_names, *arguments, &block elsif match.instantiator? scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block end @@ -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 @@ -1706,12 +1709,11 @@ MSG return unless new_attributes attributes = new_attributes.stringify_keys - role = options[:as] || :default - multi_parameter_attributes = [] + @mass_assignment_options = options unless options[:without_protection] - attributes = sanitize_for_mass_assignment(attributes, role) + attributes = sanitize_for_mass_assignment(attributes, mass_assignment_role) end attributes.each do |k, v| @@ -1724,6 +1726,7 @@ MSG end end + @mass_assignment_options = nil assign_multiparameter_attributes(multi_parameter_attributes) end @@ -1778,16 +1781,12 @@ MSG # Note also that destroying a record preserves its ID in the model instance, so deleted # models are still comparable. def ==(comparison_object) - comparison_object.equal?(self) || + super || comparison_object.instance_of?(self.class) && id.present? && comparison_object.id == id end - - # Delegates to == - def eql?(comparison_object) - self == comparison_object - end + alias :eql? :== # Delegates to id in order to allow two records of the same type and id to work with something like: # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ] @@ -1805,6 +1804,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: @@ -1883,12 +1891,33 @@ MSG value end + def mass_assignment_options + @mass_assignment_options ||= {} + end + + def mass_assignment_role + mass_assignment_options[:as] || :default + end + 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 @@ -1898,8 +1927,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 @@ -2006,7 +2036,7 @@ MSG set_values = (1..3).collect{|position| values_hash_from_param[position].blank? ? 1 : values_hash_from_param[position]} begin Date.new(*set_values) - rescue ArgumentError => ex # if Date.new raises an exception on an invalid date + rescue ArgumentError # if Date.new raises an exception on an invalid date instantiate_time_object(name, set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates end end @@ -2073,8 +2103,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/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 8d7dd8bacf..9e6cb13cca 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -707,7 +707,7 @@ module ActiveRecord def quoted_columns_for_index(column_names, options = {}) length = options[:length] if options.is_a?(Hash) - quoted_column_names = case length + case length when Hash column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) } when Fixnum diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 3e390ba994..a84f73c73f 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) + schema, table = Utils.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,13 +940,30 @@ 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}" } "DISTINCT #{columns}, #{order_columns * ', '}" end + module Utils + # 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 self.extract_schema_and_table(name) + table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse + [schema, table] + end + end + protected # Returns the version of the connected PostgreSQL server. def postgresql_version @@ -960,27 +982,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 + + 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 + # 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 +1102,14 @@ module ActiveRecord end end - def table_definition - TableDefinition.new(self) - 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/errors.rb b/activerecord/lib/active_record/errors.rb index ea1709cb1f..ad7d8cd63c 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -87,7 +87,7 @@ module ActiveRecord # # For example, in # - # Location.find :all, :conditions => ["lat = ? AND lng = ?", 53.7362] + # Location.where("lat = ? AND lng = ?", 53.7362) # # two placeholders are given but only one variable to fill them. class PreparedStatementInvalid < ActiveRecordError diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 819743a361..3f36dcde14 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -149,13 +149,13 @@ class FixturesFileNotFound < StandardError; end # self.use_transactional_fixtures = true # # test "godzilla" do -# assert !Foo.find(:all).empty? +# assert !Foo.all.empty? # Foo.destroy_all -# assert Foo.find(:all).empty? +# assert Foo.all.empty? # end # # test "godzilla aftermath" do -# assert !Foo.find(:all).empty? +# assert !Foo.all.empty? # 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 640111096d..c459846264 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -53,7 +53,7 @@ module ActiveRecord # # This migration will add a boolean flag to the accounts table and remove it # if you're backing out of the migration. It shows how all migrations have - # two class methods +up+ and +down+ that describes the transformations + # two methods +up+ and +down+ that describes the transformations # required to implement or remove the migration. These methods can consist # of both the migration specific methods like add_column and remove_column, # but may also contain regular Ruby code for generating data needed for the @@ -66,9 +66,9 @@ module ActiveRecord # create_table :system_settings do |t| # t.string :name # t.string :label - # t.text :value + # t.text :value # t.string :type - # t.integer :position + # t.integer :position # end # # SystemSetting.create :name => "notice", @@ -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 # @@ -179,7 +181,7 @@ module ActiveRecord # # class RemoveEmptyTags < ActiveRecord::Migration # def up - # Tag.find(:all).each { |tag| tag.destroy if tag.pages.empty? } + # Tag.all.each { |tag| tag.destroy if tag.pages.empty? } # end # # def down @@ -225,7 +227,7 @@ module ActiveRecord # def up # add_column :people, :salary, :integer # Person.reset_column_information - # Person.find(:all).each do |p| + # Person.all.each do |p| # p.update_attribute :salary, SalaryCalculator.compute(p) # end # end @@ -245,7 +247,7 @@ module ActiveRecord # def up # ... # say_with_time "Updating salaries..." do - # Person.find(:all).each do |p| + # Person.all.each do |p| # p.update_attribute :salary, SalaryCalculator.compute(p) # end # end diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index ed8e5ae140..2eeff7e36f 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -1,12 +1,12 @@ module ActiveRecord class Migration - # ActiveRecord::Migration::CommandRecorder records commands done during + # <tt>ActiveRecord::Migration::CommandRecorder</tt> records commands done during # a migration and knows how to reverse those commands. The CommandRecorder # knows how to invert the following commands: # # * add_column # * add_index - # * add_timestamp + # * add_timestamps # * create_table # * remove_timestamps # * rename_column @@ -23,7 +23,7 @@ module ActiveRecord # record +command+. +command+ should be a method name and arguments. # For example: # - # recorder.record(:method_name, [:arg1, arg2]) + # recorder.record(:method_name, [:arg1, :arg2]) def record(*command) @commands << command end @@ -34,7 +34,7 @@ module ActiveRecord # recorder.record(:rename_table, [:old, :new]) # recorder.inverse # => [:rename_table, [:new, :old]] # - # This method will raise an IrreversibleMigration exception if it cannot + # This method will raise an +IrreversibleMigration+ exception if it cannot # invert the +commands+. def inverse @commands.reverse.map { |name, args| @@ -48,11 +48,11 @@ module ActiveRecord super || delegate.respond_to?(*args) end - [:create_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default].each do |method| + [:create_table, :change_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default].each do |method| class_eval <<-EOV, __FILE__, __LINE__ + 1 - def #{method}(*args) - record(:"#{method}", args) - end + def #{method}(*args) # def create_table(*args) + record(:"#{method}", args) # record(:create_table, args) + end # end EOV 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/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 08b27b6a8e..53617059d0 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -277,14 +277,14 @@ module ActiveRecord type = (reflection.collection? ? :collection : :one_to_one) # def pirate_attributes=(attributes) - # assign_nested_attributes_for_one_to_one_association(:pirate, attributes) + # assign_nested_attributes_for_one_to_one_association(:pirate, attributes, mass_assignment_options) # end class_eval <<-eoruby, __FILE__, __LINE__ + 1 if method_defined?(:#{association_name}_attributes=) remove_method(:#{association_name}_attributes=) end def #{association_name}_attributes=(attributes) - assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes) + assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, mass_assignment_options) end eoruby else @@ -319,21 +319,21 @@ module ActiveRecord # If the given attributes include a matching <tt>:id</tt> attribute, or # update_only is true, and a <tt>:_destroy</tt> key set to a truthy value, # then the existing record will be marked for destruction. - def assign_nested_attributes_for_one_to_one_association(association_name, attributes) + def assign_nested_attributes_for_one_to_one_association(association_name, attributes, assignment_opts = {}) options = self.nested_attributes_options[association_name] attributes = attributes.with_indifferent_access if (options[:update_only] || !attributes['id'].blank?) && (record = send(association_name)) && (options[:update_only] || record.id.to_s == attributes['id'].to_s) - assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes) + assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy], assignment_opts) unless call_reject_if(association_name, attributes) - elsif attributes['id'].present? + elsif attributes['id'].present? && !assignment_opts[:without_protection] raise_nested_attributes_record_not_found(association_name, attributes['id']) elsif !reject_new_record?(association_name, attributes) method = "build_#{association_name}" if respond_to?(method) - send(method, attributes.except(*UNASSIGNABLE_KEYS)) + send(method, attributes.except(*unassignable_keys(assignment_opts)), assignment_opts) else raise ArgumentError, "Cannot build association #{association_name}. Are you trying to build a polymorphic one-to-one association?" end @@ -367,7 +367,7 @@ module ActiveRecord # { :name => 'John' }, # { :id => '2', :_destroy => true } # ]) - def assign_nested_attributes_for_collection_association(association_name, attributes_collection) + def assign_nested_attributes_for_collection_association(association_name, attributes_collection, assignment_opts = {}) options = self.nested_attributes_options[association_name] unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array) @@ -401,7 +401,7 @@ module ActiveRecord if attributes['id'].blank? unless reject_new_record?(association_name, attributes) - association.build(attributes.except(*UNASSIGNABLE_KEYS)) + association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts) end elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s } unless association.loaded? || call_reject_if(association_name, attributes) @@ -418,8 +418,10 @@ module ActiveRecord end if !call_reject_if(association_name, attributes) - assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) + assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy], assignment_opts) end + elsif assignment_opts[:without_protection] + association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts) else raise_nested_attributes_record_not_found(association_name, attributes['id']) end @@ -428,8 +430,8 @@ module ActiveRecord # Updates a record with the +attributes+ or marks it for destruction if # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+. - def assign_to_or_mark_for_destruction(record, attributes, allow_destroy) - record.attributes = attributes.except(*UNASSIGNABLE_KEYS) + def assign_to_or_mark_for_destruction(record, attributes, allow_destroy, assignment_opts) + record.assign_attributes(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts) record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy end @@ -458,5 +460,9 @@ module ActiveRecord def raise_nested_attributes_record_not_found(association_name, record_id) raise RecordNotFound, "Couldn't find #{self.class.reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}" end + + def unassignable_keys(assignment_opts) + assignment_opts[:without_protection] ? UNASSIGNABLE_KEYS - %w[id] : UNASSIGNABLE_KEYS + end end end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 4ec0431b8c..d9cd7987b0 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -79,6 +79,8 @@ module ActiveRecord # Deletes the record in the database and freezes this instance to reflect # that no changes should be made (since they can't be persisted). def destroy + destroy_associations + if persisted? IdentityMap.remove(self) if IdentityMap.enabled? pk = self.class.primary_key @@ -279,11 +281,16 @@ module ActiveRecord @changed_attributes.except!(*changes.keys) primary_key = self.class.primary_key - self.class.update_all(changes, { primary_key => self[primary_key] }) == 1 + self.class.unscoped.update_all(changes, { primary_key => self[primary_key] }) == 1 end end private + + # A hook to be overriden by association modules. + def destroy_associations + end + def create_or_update raise ReadOnlyRecord if readonly? result = new_record? ? create : update @@ -319,9 +326,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 6f8f84d50b..44e5520230 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -44,6 +44,12 @@ db_namespace = namespace :db do create_database(ActiveRecord::Base.configurations[Rails.env]) end + def mysql_creation_options(config) + @charset = ENV['CHARSET'] || 'utf8' + @collation = ENV['COLLATION'] || 'utf8_unicode_ci' + {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)} + end + def create_database(config) begin if config['adapter'] =~ /sqlite/ @@ -67,9 +73,6 @@ db_namespace = namespace :db do rescue case config['adapter'] when /mysql/ - @charset = ENV['CHARSET'] || 'utf8' - @collation = ENV['COLLATION'] || 'utf8_unicode_ci' - creation_options = {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)} if config['adapter'] =~ /jdbc/ #FIXME After Jdbcmysql gives this class require 'active_record/railties/jdbcmysql_error' @@ -80,7 +83,7 @@ db_namespace = namespace :db do access_denied_error = 1045 begin ActiveRecord::Base.establish_connection(config.merge('database' => nil)) - ActiveRecord::Base.connection.create_database(config['database'], creation_options) + ActiveRecord::Base.connection.create_database(config['database'], mysql_creation_options(config)) ActiveRecord::Base.establish_connection(config) rescue error_class => sqlerr if sqlerr.errno == access_denied_error @@ -369,7 +372,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 +380,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 +425,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| @@ -444,7 +446,7 @@ db_namespace = namespace :db do case abcs['test']['adapter'] when /mysql/ ActiveRecord::Base.establish_connection(:test) - ActiveRecord::Base.connection.recreate_database(abcs['test']['database'], abcs['test']) + ActiveRecord::Base.connection.recreate_database(abcs['test']['database'], mysql_creation_options(abcs['test'])) when /postgresql/ ActiveRecord::Base.clear_active_connections! drop_database(abcs['test']) @@ -453,9 +455,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| @@ -498,7 +502,7 @@ namespace :railties do # desc "Copies missing migrations from Railties (e.g. plugins, engines). You can specify Railties to use with FROM=railtie1,railtie2" task :migrations => :'db:load_config' do to_load = ENV['FROM'].blank? ? :all : ENV['FROM'].split(",").map {|n| n.strip } - railties = {} + railties = ActiveSupport::OrderedHash.new Rails.application.railties.all do |railtie| next unless to_load == :all || to_load.include?(railtie.railtie_name) diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 6df473d011..a2324039cf 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -80,12 +80,6 @@ module ActiveRecord # Abstract base class for AggregateReflection and AssociationReflection. Objects of # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods. class MacroReflection - attr_reader :active_record - - def initialize(macro, name, options, active_record) - @macro, @name, @options, @active_record = macro, name, options, active_record - end - # Returns the name of the macro. # # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:balance</tt> @@ -104,6 +98,19 @@ module ActiveRecord # <tt>has_many :clients</tt> returns +{}+ attr_reader :options + attr_reader :active_record + + attr_reader :plural_name # :nodoc: + + def initialize(macro, name, options, active_record) + @macro = macro + @name = name + @options = options + @active_record = active_record + @plural_name = active_record.pluralize_table_names ? + name.to_s.pluralize : name.to_s + end + # Returns the class for the macro. # # <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money class @@ -123,7 +130,11 @@ module ActiveRecord # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute, # and +other_aggregation+ has an options hash assigned to it. def ==(other_aggregation) - other_aggregation.kind_of?(self.class) && name == other_aggregation.name && other_aggregation.options && active_record == other_aggregation.active_record + super || + other_aggregation.kind_of?(self.class) && + name == other_aggregation.name && + other_aggregation.options && + active_record == other_aggregation.active_record end def sanitized_conditions #:nodoc: @@ -168,25 +179,8 @@ module ActiveRecord # Returns a new, unsaved instance of the associated class. +options+ will # be passed to the class's constructor. - def build_association(*options) - klass.new(*options) - end - - # Creates a new instance of the associated class, and immediately saves it - # with ActiveRecord::Base#save. +options+ will be passed to the class's - # creation method. Returns the newly created object. - def create_association(*options) - klass.create(*options) - end - - # Creates a new instance of the associated class, and immediately saves it - # with ActiveRecord::Base#save!. +options+ will be passed to the class's - # creation method. If the created record doesn't pass validations, then an - # exception will be raised. - # - # Returns the newly created object. - def create_association!(*options) - klass.create!(*options) + def build_association(*options, &block) + klass.new(*options, &block) end def table_name diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 317af8a15d..2d0861d5c9 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/module/delegation' module ActiveRecord # = Active Record Relation @@ -6,7 +7,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 +30,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) @@ -248,8 +250,7 @@ module ActiveRecord # Person.update(people.keys, people.values) def update(id, attributes) if id.is_a?(Array) - idx = -1 - id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) } + id.each.with_index.map {|one_id, idx| update(one_id, attributes[idx])} else object = find(id) object.update_attributes(attributes) @@ -403,11 +404,21 @@ 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? - @should_eager_load ||= (@eager_load_values.any? || (@includes_values.any? && references_eager_loaded_tables?)) + @should_eager_load ||= + @eager_load_values.any? || + @includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?) + end + + # Joins that are also marked for preloading. In which case we should just eager load them. + # Note that this is a naive implementation because we could have strings and symbols which + # represent the same association, but that aren't matched by this. Also, we could have + # nested hashes which partially match, e.g. { :a => :b } & { :a => [:b, :c] } + def joined_includes_values + @includes_values & @joins_values end def ==(other) diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 12004e9f43..ec1176e3dd 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -20,8 +20,6 @@ module ActiveRecord find_in_batches(options) do |records| records.each { |record| yield record } end - - self end # Yields each batch of records that was found by the find +options+ as diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index aabe5c269b..0ac821b2d7 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -89,8 +89,12 @@ module ActiveRecord # +calculate+ for examples with options. # # Person.sum('age') # => 4562 - def sum(column_name, options = {}) - calculate(:sum, column_name, options) + def sum(*args) + if block_given? + self.to_a.sum(*args) {|*block_args| yield(*block_args)} + else + calculate(:sum, *args) + end end # This calculates aggregate values in the given column. Methods for count, sum, average, @@ -146,10 +150,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 +206,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 +255,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/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index c6e8762b4a..283115a448 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -193,8 +193,8 @@ module ActiveRecord else relation = relation.where(table[primary_key].eq(id)) if id end - - connection.select_value(relation.to_sql) ? true : false + + connection.select_value(relation.to_sql, "#{name} Exists") ? true : false end protected @@ -259,6 +259,7 @@ module ActiveRecord if match.bang? && result.blank? raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}" else + yield(result) if block_given? result end end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 739363415c..8bd4732c0c 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -96,11 +96,11 @@ module ActiveRecord relation end - def having(*args) - return self if args.blank? + def having(opts, *rest) + return self if opts.blank? relation = clone - relation.having_values += build_where(*args) + relation.having_values += build_where(opts, rest) relation end @@ -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::Ordering + 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/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb index 929559c3ba..76c37cc367 100644 --- a/activerecord/lib/active_record/session_store.rb +++ b/activerecord/lib/active_record/session_store.rb @@ -183,11 +183,6 @@ module ActiveRecord ## # :singleton-method: - # Use the ActiveRecord::Base.connection by default. - cattr_accessor :connection - - ## - # :singleton-method: # The table name defaults to 'sessions'. cattr_accessor :table_name @@table_name = 'sessions' @@ -206,10 +201,19 @@ module ActiveRecord class << self alias :data_column_name :data_column + + # Use the ActiveRecord::Base.connection by default. + attr_writer :connection + + # Use the ActiveRecord::Base.connection_pool by default. + attr_writer :connection_pool - remove_method :connection def connection - @@connection ||= ActiveRecord::Base.connection + @connection ||= ActiveRecord::Base.connection + end + + def connection_pool + @connection_pool ||= ActiveRecord::Base.connection_pool end # Look up a session by id and unmarshal its data if found. @@ -219,6 +223,8 @@ module ActiveRecord end end end + + delegate :connection, :connection=, :connection_pool, :connection_pool=, :to => self attr_reader :session_id, :new_record alias :new_record? :new_record @@ -297,8 +303,12 @@ module ActiveRecord private def get_session(env, sid) Base.silence do - sid ||= generate_sid - session = find_session(sid) + unless sid and session = @@session_class.find_by_session_id(sid) + # If the sid was nil or if there is no pre-existing session under the sid, + # force the generation of a new sid and associate a new session associated with the new sid + sid = generate_sid + session = @@session_class.new(:session_id => sid, :data => {}) + end env[SESSION_RECORD_KEY] = session [sid, session.data] end diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb index c61428e104..ffe9b08dce 100644 --- a/activerecord/lib/active_record/test_case.rb +++ b/activerecord/lib/active_record/test_case.rb @@ -31,27 +31,30 @@ module ActiveRecord end def assert_sql(*patterns_to_match) - $queries_executed = [] + ActiveRecord::SQLCounter.log = [] yield - $queries_executed + ActiveRecord::SQLCounter.log ensure failed_patterns = [] patterns_to_match.each do |pattern| - failed_patterns << pattern unless $queries_executed.any?{ |sql| pattern === sql } + failed_patterns << pattern unless ActiveRecord::SQLCounter.log.any?{ |sql| pattern === sql } end - assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{$queries_executed.size == 0 ? '' : "\nQueries:\n#{$queries_executed.join("\n")}"}" + assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{ActiveRecord::SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{ActiveRecord::SQLCounter.log.join("\n")}"}" end def assert_queries(num = 1) - $queries_executed = [] + ActiveRecord::SQLCounter.log = [] yield ensure - %w{ BEGIN COMMIT }.each { |x| $queries_executed.delete(x) } - assert_equal num, $queries_executed.size, "#{$queries_executed.size} instead of #{num} queries were executed.#{$queries_executed.size == 0 ? '' : "\nQueries:\n#{$queries_executed.join("\n")}"}" + assert_equal num, ActiveRecord::SQLCounter.log.size, "#{ActiveRecord::SQLCounter.log.size} instead of #{num} queries were executed.#{ActiveRecord::SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{ActiveRecord::SQLCounter.log.join("\n")}"}" end def assert_no_queries(&block) + prev_ignored_sql = ActiveRecord::SQLCounter.ignored_sql + ActiveRecord::SQLCounter.ignored_sql = [] assert_queries(0, &block) + ensure + ActiveRecord::SQLCounter.ignored_sql = prev_ignored_sql end def with_kcode(kcode) diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index d363f36108..ae97a3f3ca 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -165,7 +165,7 @@ module ActiveRecord # writing, the only database that we're aware of that supports true nested # transactions, is MS-SQL. Because of this, Active Record emulates nested # transactions by using savepoints on MySQL and PostgreSQL. See - # http://dev.mysql.com/doc/refman/5.0/en/savepoints.html + # http://dev.mysql.com/doc/refman/5.0/en/savepoint.html # for more information about savepoints. # # === Callbacks diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb index 43fe1cdee5..838aa8fb1e 100644 --- a/activerecord/lib/active_record/version.rb +++ b/activerecord/lib/active_record/version.rb @@ -1,9 +1,9 @@ module ActiveRecord module VERSION #:nodoc: MAJOR = 3 - MINOR = 1 + MINOR = 2 TINY = 0 - PRE = "rc1" + PRE = "beta" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index e7a3bb9ced..4c65193d75 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" class AdapterTest < ActiveRecord::TestCase def setup @@ -146,4 +146,18 @@ class AdapterTest < ActiveRecord::TestCase end end end + + def test_disable_referential_integrity + assert_nothing_raised do + @connection.disable_referential_integrity do + # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method + if @connection.prefetch_primary_key? + id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id")) + @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)" + else + @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)" + end + end + end + end end diff --git a/activerecord/test/cases/adapters/firebird/connection_test.rb b/activerecord/test/cases/adapters/firebird/connection_test.rb index 47e8859ac6..f57ea686a5 100644 --- a/activerecord/test/cases/adapters/firebird/connection_test.rb +++ b/activerecord/test/cases/adapters/firebird/connection_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" class FirebirdConnectionTest < ActiveRecord::TestCase def test_charset_properly_set diff --git a/activerecord/test/cases/adapters/firebird/default_test.rb b/activerecord/test/cases/adapters/firebird/default_test.rb index 2d5e45dd74..713c7e11bf 100644 --- a/activerecord/test/cases/adapters/firebird/default_test.rb +++ b/activerecord/test/cases/adapters/firebird/default_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" require 'models/default' class DefaultTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/adapters/firebird/migration_test.rb b/activerecord/test/cases/adapters/firebird/migration_test.rb index 0fe3d4a858..5c94593765 100644 --- a/activerecord/test/cases/adapters/firebird/migration_test.rb +++ b/activerecord/test/cases/adapters/firebird/migration_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" require 'models/course' class FirebirdMigrationTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index c817b259b4..509baacaef 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" class ActiveSchemaTest < ActiveRecord::TestCase def setup diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index 703eb5a8bd..eee771ecff 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" class MysqlConnectionTest < ActiveRecord::TestCase def setup diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb index 756115e5c9..146b77a95c 100644 --- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb @@ -1,6 +1,6 @@ # encoding: utf-8 -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" module ActiveRecord module ConnectionAdapters diff --git a/activerecord/test/cases/adapters/mysql/quoting_test.rb b/activerecord/test/cases/adapters/mysql/quoting_test.rb index 7933800440..3d1330efb8 100644 --- a/activerecord/test/cases/adapters/mysql/quoting_test.rb +++ b/activerecord/test/cases/adapters/mysql/quoting_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" module ActiveRecord module ConnectionAdapters diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb index 2750831b93..292c7efebb 100644 --- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" class Group < ActiveRecord::Base Group.table_name = 'group' diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb index 8b26d303bf..a2155d1dd1 100644 --- a/activerecord/test/cases/adapters/mysql/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/schema_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" require 'models/post' require 'models/comment' diff --git a/activerecord/test/cases/adapters/mysql/sp_test.rb b/activerecord/test/cases/adapters/mysql/sp_test.rb index baa44fab70..3ca2917ca4 100644 --- a/activerecord/test/cases/adapters/mysql/sp_test.rb +++ b/activerecord/test/cases/adapters/mysql/sp_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" require 'models/topic' class StoredProcedureTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index ca716f7016..a83399d0cd 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" class ActiveSchemaTest < ActiveRecord::TestCase def setup diff --git a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb index 7bb35d755a..cd9c1041dc 100644 --- a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb +++ b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" require 'models/topic' module ActiveRecord diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 1c523dad69..26091c713b 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" class MysqlConnectionTest < ActiveRecord::TestCase def setup diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb index 9cb30b0fd1..3a9744e78f 100644 --- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" class Group < ActiveRecord::Base Group.table_name = 'group' @@ -87,8 +87,8 @@ class MysqlReservedWordTest < ActiveRecord::TestCase assert_nothing_raised { x.save } x.order = 'y' assert_nothing_raised { x.save } - assert_nothing_raised { y = Group.find_by_order('y') } - assert_nothing_raised { y = Group.find(1) } + assert_nothing_raised { Group.find_by_order('y') } + assert_nothing_raised { Group.find(1) } x = Group.find(1) end diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb index ec89972360..858d1da2dd 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" require 'models/post' require 'models/comment' diff --git a/activerecord/test/cases/adapters/oracle/synonym_test.rb b/activerecord/test/cases/adapters/oracle/synonym_test.rb index 4cbca6dcb2..b9a422a6ca 100644 --- a/activerecord/test/cases/adapters/oracle/synonym_test.rb +++ b/activerecord/test/cases/adapters/oracle/synonym_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" require 'models/topic' require 'models/subject' diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb index 1802e2be24..e4746d4aa3 100644 --- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require 'cases/helper' class PostgresqlActiveSchemaTest < ActiveRecord::TestCase def setup diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index c383e8b6fb..ce08e4c6a7 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" class PostgresqlArray < ActiveRecord::Base end diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 14e78ed982..d57794daf8 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -1,5 +1,5 @@ # encoding: utf-8 -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" module ActiveRecord module ConnectionAdapters @@ -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/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb index e95d10c594..172055f15c 100644 --- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb +++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" module ActiveRecord module ConnectionAdapters diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb index 8b9c4d337a..d5e1838543 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" class SchemaThing < ActiveRecord::Base end diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index 6f3dba4168..3a7f1badf0 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" class SchemaTest < ActiveRecord::TestCase self.use_transactional_fixtures = false @@ -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, ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::Utils.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/timestamp_test.rb b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb index fb4616dc76..337f43c421 100644 --- a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb +++ b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require 'cases/helper' require 'models/developer' class TimestampTest < ActiveRecord::TestCase 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/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb index 899df1d59a..575b4806c1 100644 --- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" class CopyTableTest < ActiveRecord::TestCase fixtures :customers, :companies, :comments diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index 51f01c262d..0d9db92447 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" require 'bigdecimal' require 'yaml' diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index d58a804815..6ff04e3eb3 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -1,5 +1,5 @@ # encoding: utf-8 -require File.expand_path('../../../helper', __FILE__) +require "cases/helper" module ActiveRecord module ConnectionAdapters diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb index d296364d7f..3e0e6dce2c 100644 --- a/activerecord/test/cases/aggregations_test.rb +++ b/activerecord/test/cases/aggregations_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/customer' require 'active_support/core_ext/exception' diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb index eb4488899f..588adc38e3 100644 --- a/activerecord/test/cases/ar_schema_test.rb +++ b/activerecord/test/cases/ar_schema_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" if ActiveRecord::Base.connection.supports_migrations? diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index d7293aa1e3..818902beb5 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require "cases/helper" require 'models/developer' require 'models/project' require 'models/company' @@ -296,6 +296,15 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal 1, Topic.find(topic.id)[:replies_count] end + def test_belongs_to_counter_when_update_column + topic = Topic.create!(:title => "37s") + topic.replies.create!(:title => "re: 37s", :content => "rails") + assert_equal 1, Topic.find(topic.id)[:replies_count] + + topic.update_column(:content, "rails is wonderfull") + assert_equal 1, Topic.find(topic.id)[:replies_count] + end + def test_assignment_before_child_saved final_cut = Client.new("name" => "Final Cut") firm = Firm.find(1) @@ -658,4 +667,27 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase firm = client.create_firm!{ |f| f.name = 'Agency Company' } assert_equal 'Agency Company', firm.name end + + def test_should_set_foreign_key_on_create_association + client = Client.create! :name => "fuu" + + firm = client.create_firm :name => "baa" + assert_equal firm.id, client.client_of + end + + def test_should_set_foreign_key_on_create_association! + client = Client.create! :name => "fuu" + + firm = client.create_firm! :name => "baa" + assert_equal firm.id, client.client_of + end + + def test_self_referential_belongs_to_with_counter_cache_assigning_nil + comment = Comment.create! :post => posts(:thinking), :body => "fuu" + comment.parent = nil + comment.save! + + assert_equal nil, comment.reload.parent + assert_equal 0, comments(:greetings).reload.children_count + end end diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb index 387a25b7b1..2d0d4541b4 100644 --- a/activerecord/test/cases/associations/callbacks_test.rb +++ b/activerecord/test/cases/associations/callbacks_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require "cases/helper" require 'models/post' require 'models/author' require 'models/project' diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index d21afff978..ff376a68d8 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require "cases/helper" require 'models/post' require 'models/comment' require 'models/author' diff --git a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb index 41cf745f91..d75791cab9 100644 --- a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb +++ b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require 'cases/helper' require 'models/post' require 'models/tagging' diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb index de1f47873f..2cf9f89c3c 100644 --- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb +++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require 'cases/helper' require 'models/post' require 'models/tag' require 'models/author' diff --git a/activerecord/test/cases/associations/eager_singularization_test.rb b/activerecord/test/cases/associations/eager_singularization_test.rb index ffb5411a5b..07d0b24613 100644 --- a/activerecord/test/cases/associations/eager_singularization_test.rb +++ b/activerecord/test/cases/associations/eager_singularization_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require "cases/helper" class Virus < ActiveRecord::Base belongs_to :octopus diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 945edad48a..f1d061133c 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require "cases/helper" require 'models/post' require 'models/tagging' require 'models/tag' @@ -1040,4 +1040,12 @@ class EagerAssociationTest < ActiveRecord::TestCase } assert_no_queries { assert_equal groucho, sponsor.thing } end + + def test_joins_with_includes_should_preload_via_joins + post = assert_queries(1) { Post.includes(:comments).joins(:comments).order('posts.id desc').to_a.first } + + assert_queries(0) do + assert_not_equal 0, post.comments.to_a.count + end + end end diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb index c0a8f74834..24830a661a 100644 --- a/activerecord/test/cases/associations/extension_test.rb +++ b/activerecord/test/cases/associations/extension_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require "cases/helper" require 'models/post' require 'models/comment' require 'models/project' diff --git a/activerecord/test/cases/associations/habtm_join_table_test.rb b/activerecord/test/cases/associations/habtm_join_table_test.rb index cc0a6cd6c2..fe2b82f2c1 100644 --- a/activerecord/test/cases/associations/habtm_join_table_test.rb +++ b/activerecord/test/cases/associations/habtm_join_table_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require 'cases/helper' class MyReader < ActiveRecord::Base has_and_belongs_to_many :my_books diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 171177ad1d..d8d2a113ff 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require "cases/helper" require 'models/developer' require 'models/project' require 'models/company' diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index b87a1744c1..a2764f3e3b 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require "cases/helper" require 'models/developer' require 'models/project' require 'models/company' @@ -226,6 +226,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 2, Firm.find(:first, :order => "id").clients.length end + def test_finding_array_compatibility + assert_equal 2, Firm.order(:id).find{|f| f.id > 0}.clients.length + end + def test_find_with_blank_conditions [[], {}, nil, ""].each do |blank| assert_equal 2, Firm.find(:first, :order => "id").clients.find(:all, :conditions => blank).size @@ -537,6 +541,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 3, companies(:first_firm).clients_of_firm(true).size end + def test_transactions_when_adding_to_persisted + good = Client.new(:name => "Good") + bad = Client.new(:name => "Bad", :raise_on_save => true) + + begin + companies(:first_firm).clients_of_firm.concat(good, bad) + rescue Client::RaisedOnSave + end + + assert !companies(:first_firm).clients_of_firm(true).include?(good) + end + + def test_transactions_when_adding_to_new_record + assert_no_queries do + firm = Firm.new + firm.clients_of_firm.concat(Client.new("name" => "Natural Company")) + end + end + def test_new_aliased_to_build company = companies(:first_firm) new_client = assert_no_queries { company.clients_of_firm.new("name" => "Another Client") } @@ -778,6 +801,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 0, companies(:first_firm).clients_of_firm(true).size end + def test_transaction_when_deleting_persisted + good = Client.new(:name => "Good") + bad = Client.new(:name => "Bad", :raise_on_destroy => true) + + companies(:first_firm).clients_of_firm = [good, bad] + + begin + companies(:first_firm).clients_of_firm.destroy(good, bad) + rescue Client::RaisedOnDestroy + end + + assert_equal [good, bad], companies(:first_firm).clients_of_firm(true) + end + + def test_transaction_when_deleting_new_record + assert_no_queries do + firm = Firm.new + client = Client.new("name" => "New Client") + firm.clients_of_firm << client + firm.clients_of_firm.destroy(client) + end + end + def test_clearing_an_association_collection firm = companies(:first_firm) client_id = firm.clients_of_firm.first.id @@ -1111,6 +1157,27 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal orig_accounts, firm.accounts end + def test_transactions_when_replacing_on_persisted + good = Client.new(:name => "Good") + bad = Client.new(:name => "Bad", :raise_on_save => true) + + companies(:first_firm).clients_of_firm = [good] + + begin + companies(:first_firm).clients_of_firm = [bad] + rescue Client::RaisedOnSave + end + + assert_equal [good], companies(:first_firm).clients_of_firm(true) + end + + def test_transactions_when_replacing_on_new_record + assert_no_queries do + firm = Firm.new + firm.clients_of_firm = [Client.new("name" => "New Client")] + end + end + def test_get_ids assert_equal [companies(:first_client).id, companies(:second_client).id], companies(:first_firm).client_ids end @@ -1494,4 +1561,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 1, contract.hi_count assert_equal 1, contract.bye_count end + + def test_association_attributes_are_available_to_after_initialize + car = Car.create(:name => 'honda') + bulb = car.bulbs.build + + assert_equal car.id, bulb.attributes_after_initialize['car_id'] + end end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 476bc637f6..0b1ba31ac2 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require "cases/helper" require 'models/post' require 'models/person' require 'models/reference' @@ -714,6 +714,11 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_equal [categories(:general).id], authors(:mary).categories_like_general_ids end + def test_get_collection_singular_ids_on_has_many_through_with_conditions_and_include + person = Person.first + assert_equal person.posts_with_no_comment_ids, person.posts_with_no_comments.map(&:id) + end + def test_count_has_many_through_with_named_scope assert_equal 2, authors(:mary).categories.count assert_equal 1, authors(:mary).categories.general.count diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 9bafe0f57d..26931e3e85 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require "cases/helper" require 'models/developer' require 'models/project' require 'models/company' @@ -345,6 +345,17 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert orig_ship.destroyed? end + def test_creation_failure_due_to_new_record_should_raise_error + pirate = pirates(:redbeard) + new_ship = Ship.new + + assert_raise(ActiveRecord::RecordNotSaved) do + pirate.ship = new_ship + end + assert_nil pirate.ship + assert_nil new_ship.pirate_id + end + def test_replacement_failure_due_to_existing_record_should_raise_error pirate = pirates(:blackbeard) pirate.ship.name = nil @@ -438,4 +449,11 @@ class HasOneAssociationsTest < ActiveRecord::TestCase bulb = car.create_bulb!{ |b| b.color = 'Red' } assert_equal 'RED!', bulb.color end + + def test_association_attributes_are_available_to_after_initialize + car = Car.create(:name => 'honda') + bulb = car.create_bulb + + assert_equal car.id, bulb.attributes_after_initialize['car_id'] + end end diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb index 24576fb580..2503349c08 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require "cases/helper" require 'models/club' require 'models/member_type' require 'models/member' diff --git a/activerecord/test/cases/associations/identity_map_test.rb b/activerecord/test/cases/associations/identity_map_test.rb index 0dd6c76efd..9b8635774c 100644 --- a/activerecord/test/cases/associations/identity_map_test.rb +++ b/activerecord/test/cases/associations/identity_map_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require "cases/helper" require 'models/author' require 'models/post' diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb index 1c0fe8368f..e5e9ca6131 100644 --- a/activerecord/test/cases/associations/inner_join_association_test.rb +++ b/activerecord/test/cases/associations/inner_join_association_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require "cases/helper" require 'models/post' require 'models/comment' require 'models/author' diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb index 9e4975566b..76282213d8 100644 --- a/activerecord/test/cases/associations/inverse_associations_test.rb +++ b/activerecord/test/cases/associations/inverse_associations_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require "cases/helper" require 'models/man' require 'models/face' require 'models/interest' diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index c869e6fbc5..4ce8b85098 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require "cases/helper" require 'active_support/core_ext/object/inclusion' require 'models/tag' require 'models/tagging' @@ -139,7 +139,21 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_set_polymorphic_has_one tagging = tags(:misc).taggings.create posts(:thinking).tagging = tagging - assert_equal "Post", tagging.taggable_type + + assert_equal "Post", tagging.taggable_type + assert_equal posts(:thinking).id, tagging.taggable_id + assert_equal posts(:thinking), tagging.taggable + end + + def test_set_polymorphic_has_one_on_new_record + tagging = tags(:misc).taggings.create + post = Post.new :title => "foo", :body => "bar" + post.tagging = tagging + post.save! + + assert_equal "Post", tagging.taggable_type + assert_equal post.id, tagging.taggable_id + assert_equal post, tagging.taggable end def test_create_polymorphic_has_many_with_scope @@ -708,8 +722,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_with_pluralize_table_names_false - engine = Engine.create!(:car_id => 1) - aircraft = Aircraft.create!(:name => "Airbus 380", :id => 1) + aircraft = Aircraft.create!(:name => "Airbus 380") + engine = Engine.create!(:car_id => aircraft.id) assert_equal aircraft.engines, [engine] end diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb index b13b8d5e36..dd450a2a8e 100644 --- a/activerecord/test/cases/associations/nested_through_associations_test.rb +++ b/activerecord/test/cases/associations/nested_through_associations_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require "cases/helper" require 'models/author' require 'models/post' require 'models/person' diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index 8bff37907d..49d82ba2df 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/developer' require 'models/project' require 'models/company' diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb index bb0dcb2a77..3641031d12 100644 --- a/activerecord/test/cases/attribute_methods/read_test.rb +++ b/activerecord/test/cases/attribute_methods/read_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require "cases/helper" require 'active_support/core_ext/object/inclusion' module ActiveRecord diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index bf6408be0f..b0896fb236 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'active_support/core_ext/object/inclusion' require 'models/minimalistic' require 'models/developer' diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index b14c458435..4ad2cdfc7e 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require 'cases/helper' require 'models/bird' require 'models/company' require 'models/customer' @@ -837,7 +837,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase @pirate.parrots.each { |parrot| parrot.mark_for_destruction } assert @pirate.save - assert_no_queries do + assert_queries(0) do assert @pirate.save end end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 5eb9a825b7..9feb7b010f 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/post' require 'models/author' require 'models/topic' @@ -53,6 +53,16 @@ class Weird < ActiveRecord::Base; end class Boolean < ActiveRecord::Base; end +class LintTest < ActiveRecord::TestCase + include ActiveModel::Lint::Tests + + class LintModel < ActiveRecord::Base; end + + def setup + @model = LintModel.new + end +end + class BasicsTest < ActiveRecord::TestCase fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts @@ -478,6 +488,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/batches_test.rb b/activerecord/test/cases/batches_test.rb index 102b8d69da..a35baee4ed 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require 'cases/helper' require 'models/post' class EachTest < ActiveRecord::TestCase @@ -18,6 +18,13 @@ class EachTest < ActiveRecord::TestCase end end + def test_each_should_not_return_query_chain_and_execcute_only_one_query + assert_queries(1) do + result = Post.find_each(:batch_size => 100000){ } + assert_nil result + end + end + def test_each_should_raise_if_select_is_set_without_id assert_raise(RuntimeError) do Post.find_each(:select => :title, :batch_size => 1) { |post| post } diff --git a/activerecord/test/cases/binary_test.rb b/activerecord/test/cases/binary_test.rb index 365c16679e..06c14cb108 100644 --- a/activerecord/test/cases/binary_test.rb +++ b/activerecord/test/cases/binary_test.rb @@ -1,5 +1,5 @@ # encoding: utf-8 -require File.expand_path('../helper', __FILE__) +require "cases/helper" # Without using prepared statements, it makes no sense to test # BLOB data with DB2 or Firebird, because the length of a statement diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb index bfd4c2afdf..3652255c38 100644 --- a/activerecord/test/cases/bind_parameter_test.rb +++ b/activerecord/test/cases/bind_parameter_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require 'cases/helper' require 'models/topic' module ActiveRecord diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index cedd5c3d41..224b3f3d1f 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/company' require 'models/topic' require 'models/edge' @@ -397,6 +397,10 @@ class CalculationsTest < ActiveRecord::TestCase Account.sum(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50") end + def test_sum_array_compatibility + assert_equal Account.sum(:credit_limit), Account.sum(&:credit_limit) + end + def test_average_with_from_option assert_equal Account.average(:credit_limit), Account.average(:credit_limit, :from => 'accounts') assert_equal Account.average(:credit_limit, :conditions => "credit_limit > 50"), diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb index c2b43438fc..7f4d25790b 100644 --- a/activerecord/test/cases/callbacks_test.rb +++ b/activerecord/test/cases/callbacks_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" class CallbackDeveloper < ActiveRecord::Base set_table_name 'developers' diff --git a/activerecord/test/cases/clone_test.rb b/activerecord/test/cases/clone_test.rb index b25340c205..d91646efca 100644 --- a/activerecord/test/cases/clone_test.rb +++ b/activerecord/test/cases/clone_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/topic' module ActiveRecord diff --git a/activerecord/test/cases/coders/yaml_column_test.rb b/activerecord/test/cases/coders/yaml_column_test.rb index 55aba713e8..c7dcc21809 100644 --- a/activerecord/test/cases/coders/yaml_column_test.rb +++ b/activerecord/test/cases/coders/yaml_column_test.rb @@ -1,4 +1,5 @@ -require File.expand_path('../../helper', __FILE__) + +require "cases/helper" module ActiveRecord module Coders diff --git a/activerecord/test/cases/column_alias_test.rb b/activerecord/test/cases/column_alias_test.rb index 1b6ac870e4..40707d9cb2 100644 --- a/activerecord/test/cases/column_alias_test.rb +++ b/activerecord/test/cases/column_alias_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/topic' class TestColumnAlias < ActiveRecord::TestCase diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb index 4d046eb9ba..d1dddd4c2c 100644 --- a/activerecord/test/cases/column_definition_test.rb +++ b/activerecord/test/cases/column_definition_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" module ActiveRecord module ConnectionAdapters diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index 0bf33f86cf..abf317768f 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require "cases/helper" module ActiveRecord module ConnectionAdapters diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb index 2b2deb6732..a1d1177289 100644 --- a/activerecord/test/cases/connection_management_test.rb +++ b/activerecord/test/cases/connection_management_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" module ActiveRecord module ConnectionAdapters @@ -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/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index 7aa765dc4f..f92f4e62c5 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" module ActiveRecord module ConnectionAdapters diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb index 31faef5f0f..3ed96a3ec8 100644 --- a/activerecord/test/cases/counter_cache_test.rb +++ b/activerecord/test/cases/counter_cache_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require 'cases/helper' require 'models/topic' require 'models/car' require 'models/wheel' diff --git a/activerecord/test/cases/custom_locking_test.rb b/activerecord/test/cases/custom_locking_test.rb index 1d4385eac0..d63ecdbcc5 100644 --- a/activerecord/test/cases/custom_locking_test.rb +++ b/activerecord/test/cases/custom_locking_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/person' module ActiveRecord diff --git a/activerecord/test/cases/database_statements_test.rb b/activerecord/test/cases/database_statements_test.rb index 8fa2c9ca44..c689e97d83 100644 --- a/activerecord/test/cases/database_statements_test.rb +++ b/activerecord/test/cases/database_statements_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" class DatabaseStatementsTest < ActiveRecord::TestCase def setup diff --git a/activerecord/test/cases/date_time_test.rb b/activerecord/test/cases/date_time_test.rb index a9e477bd76..3deb0dac99 100644 --- a/activerecord/test/cases/date_time_test.rb +++ b/activerecord/test/cases/date_time_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/topic' require 'models/task' diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb index 8cf008871b..b3a281d960 100644 --- a/activerecord/test/cases/defaults_test.rb +++ b/activerecord/test/cases/defaults_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'active_support/core_ext/object/inclusion' require 'models/default' require 'models/entrant' diff --git a/activerecord/test/cases/deprecated_finder_test.rb b/activerecord/test/cases/deprecated_finder_test.rb index 1819bfc5ca..2afc91b769 100644 --- a/activerecord/test/cases/deprecated_finder_test.rb +++ b/activerecord/test/cases/deprecated_finder_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/entrant' class DeprecatedFinderTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 7d58781eca..b1ce846218 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require 'cases/helper' require 'models/topic' # For booleans require 'models/pirate' # For timestamps require 'models/parrot' diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb index 9acf5fdd32..0236f9b0a1 100644 --- a/activerecord/test/cases/dup_test.rb +++ b/activerecord/test/cases/dup_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/topic' module ActiveRecord diff --git a/activerecord/test/cases/dynamic_finder_match_test.rb b/activerecord/test/cases/dynamic_finder_match_test.rb index 693a8e2180..e576870317 100644 --- a/activerecord/test/cases/dynamic_finder_match_test.rb +++ b/activerecord/test/cases/dynamic_finder_match_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" module ActiveRecord class DynamicFinderMatchTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/finder_respond_to_test.rb b/activerecord/test/cases/finder_respond_to_test.rb index 7952e9a46a..235805a67c 100644 --- a/activerecord/test/cases/finder_respond_to_test.rb +++ b/activerecord/test/cases/finder_respond_to_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/topic' class FinderRespondToTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 3920f306f9..3d2a03d2b9 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/post' require 'models/author' require 'models/categorization' @@ -140,23 +140,30 @@ class FinderTest < ActiveRecord::TestCase def test_find_with_group - developers = Developer.find(:all, :group => "salary", :select => "salary") + developers = Developer.find(:all, :group => "salary", :select => "salary") assert_equal 4, developers.size assert_equal 4, developers.map(&:salary).uniq.size end def test_find_with_group_and_having - developers = Developer.find(:all, :group => "salary", :having => "sum(salary) > 10000", :select => "salary") + developers = Developer.find(:all, :group => "salary", :having => "sum(salary) > 10000", :select => "salary") assert_equal 3, developers.size assert_equal 3, developers.map(&:salary).uniq.size - assert developers.all? { |developer| developer.salary > 10000 } + assert developers.all? { |developer| developer.salary > 10000 } end def test_find_with_group_and_sanitized_having - developers = Developer.find(:all, :group => "salary", :having => ["sum(salary) > ?", 10000], :select => "salary") + developers = Developer.find(:all, :group => "salary", :having => ["sum(salary) > ?", 10000], :select => "salary") assert_equal 3, developers.size assert_equal 3, developers.map(&:salary).uniq.size - assert developers.all? { |developer| developer.salary > 10000 } + assert developers.all? { |developer| developer.salary > 10000 } + end + + def test_find_with_group_and_sanitized_having_method + developers = Developer.group(:salary).having("sum(salary) > ?", 10000).select('salary').all + assert_equal 3, developers.size + assert_equal 3, developers.map(&:salary).uniq.size + assert developers.all? { |developer| developer.salary > 10000 } end def test_find_with_entire_select_statement diff --git a/activerecord/test/cases/fixtures/file_test.rb b/activerecord/test/cases/fixtures/file_test.rb index 9ff4455394..e623fbe4d1 100644 --- a/activerecord/test/cases/fixtures/file_test.rb +++ b/activerecord/test/cases/fixtures/file_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require 'cases/helper' require 'tempfile' module ActiveRecord diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index e645a9a47c..842e8a0049 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/post' require 'models/binary' require 'models/topic' @@ -178,11 +178,9 @@ class FixturesTest < ActiveRecord::TestCase assert_nothing_raised do fixtures = ActiveRecord::Fixtures.new(Account.connection, 'categories', 'Category', FIXTURES_ROOT + "/categories_ordered") - i = 0 - fixtures.each do |name, fixture| + fixtures.each.with_index do |(name, fixture), i| assert_equal "fixture_no_#{i}", name assert_equal "Category #{i}", fixture['name'] - i += 1 end end end @@ -589,8 +587,8 @@ class FoxyFixturesTest < ActiveRecord::TestCase end def test_preserves_existing_fixture_data - assert_equal(2.weeks.ago.to_date, pirates(:redbeard).created_on.to_date) - assert_equal(2.weeks.ago.to_date, pirates(:redbeard).updated_on.to_date) + assert_equal(2.weeks.ago.utc.to_date, pirates(:redbeard).created_on.utc.to_date) + assert_equal(2.weeks.ago.utc.to_date, pirates(:redbeard).updated_on.utc.to_date) end def test_generates_unique_ids diff --git a/activerecord/test/cases/habtm_destroy_order_test.rb b/activerecord/test/cases/habtm_destroy_order_test.rb index 776ed3f517..2ce0de360e 100644 --- a/activerecord/test/cases/habtm_destroy_order_test.rb +++ b/activerecord/test/cases/habtm_destroy_order_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require "models/lesson" require "models/student" @@ -16,6 +16,16 @@ class HabtmDestroyOrderTest < ActiveRecord::TestCase assert !sicp.destroyed? end + test 'should not raise error if have foreign key in the join table' do + student = Student.new(:name => "Ben Bitdiddle") + lesson = Lesson.new(:name => "SICP") + lesson.students << student + lesson.save! + assert_nothing_raised do + student.destroy + end + end + test "not destroying a student with lessons leaves student<=>lesson association intact" do # test a normal before_destroy doesn't destroy the habtm joins begin diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 7a214fa75a..6735bc521b 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -1,11 +1,5 @@ require File.expand_path('../../../../load_paths', __FILE__) -test = File.expand_path('../..', __FILE__) -$:.unshift(test) unless $:.include?('test') || $:.include?(test) - -lib = File.expand_path("#{File.dirname(__FILE__)}/../../lib") -$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) - require 'config' require 'test/unit' @@ -18,19 +12,20 @@ require 'active_support/dependencies' require 'support/config' require 'support/connection' -ARTest.connect - # TODO: Move all these random hacks into the ARTest namespace and into the support/ dir # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true -# Quote "type" if it's a reserved word for the current connection. -QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type') - # Enable Identity Map only when ENV['IM'] is set to "true" ActiveRecord::IdentityMap.enabled = (ENV['IM'] == "true") +# Connect to the database +ARTest.connect + +# Quote "type" if it's a reserved word for the current connection. +QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type') + def current_adapter?(*types) types.any? do |type| ActiveRecord::ConnectionAdapters.const_defined?(type) && @@ -63,15 +58,15 @@ end module ActiveRecord class SQLCounter - IGNORED_SQL = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/] + cattr_accessor :ignored_sql + self.ignored_sql = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/] # FIXME: this needs to be refactored so specific database can add their own # ignored SQL. This ignored SQL is for Oracle. - IGNORED_SQL.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im] + ignored_sql.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im] - def initialize - $queries_executed = [] - end + cattr_accessor :log + self.log = [] def call(name, start, finish, message_id, values) sql = values[:sql] @@ -79,10 +74,11 @@ module ActiveRecord # FIXME: this seems bad. we should probably have a better way to indicate # the query was cached unless 'CACHE' == values[:name] - $queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r } + self.class.log << sql unless self.class.ignored_sql.any? { |r| sql =~ r } end end end + ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new) end diff --git a/activerecord/test/cases/i18n_test.rb b/activerecord/test/cases/i18n_test.rb index d96dc5fba9..a428f1d87b 100644 --- a/activerecord/test/cases/i18n_test.rb +++ b/activerecord/test/cases/i18n_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/topic' require 'models/reply' diff --git a/activerecord/test/cases/identity_map/middleware_test.rb b/activerecord/test/cases/identity_map/middleware_test.rb index 047192c244..60dcad4586 100644 --- a/activerecord/test/cases/identity_map/middleware_test.rb +++ b/activerecord/test/cases/identity_map/middleware_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require "cases/helper" module ActiveRecord module IdentityMap diff --git a/activerecord/test/cases/identity_map_test.rb b/activerecord/test/cases/identity_map_test.rb index d814f1cc92..cd9c358799 100644 --- a/activerecord/test/cases/identity_map_test.rb +++ b/activerecord/test/cases/identity_map_test.rb @@ -1,4 +1,5 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" + require 'models/developer' require 'models/project' require 'models/company' diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index 5bfc572ea8..b5d8314541 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/company' require 'models/project' require 'models/subscriber' diff --git a/activerecord/test/cases/invalid_date_test.rb b/activerecord/test/cases/invalid_date_test.rb index 65d326bc12..2de50b224c 100644 --- a/activerecord/test/cases/invalid_date_test.rb +++ b/activerecord/test/cases/invalid_date_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require 'cases/helper' require 'models/topic' class InvalidDateTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb index d145ff7427..afec64750e 100644 --- a/activerecord/test/cases/invertible_migration_test.rb +++ b/activerecord/test/cases/invertible_migration_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" module ActiveRecord class InvertibleMigrationTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb index aef7403a09..d9e350abc0 100644 --- a/activerecord/test/cases/json_serialization_test.rb +++ b/activerecord/test/cases/json_serialization_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/contact' require 'models/post' require 'models/author' diff --git a/activerecord/test/cases/lifecycle_test.rb b/activerecord/test/cases/lifecycle_test.rb index 7d47f7e21b..643e949087 100644 --- a/activerecord/test/cases/lifecycle_test.rb +++ b/activerecord/test/cases/lifecycle_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require 'cases/helper' require 'models/topic' require 'models/developer' require 'models/reply' diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index 88861df86a..61baa55027 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -1,5 +1,5 @@ require 'thread' -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/person' require 'models/job' require 'models/reader' diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index f29ced4526..9e8475465e 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require "models/developer" require "models/post" require "active_support/log_subscriber/test_helper" @@ -63,6 +63,14 @@ class LogSubscriberTest < ActiveRecord::TestCase assert_match(/SELECT .*?FROM .?developers.?/i, @logger.logged(:debug).last) end + def test_exists_query_logging + Developer.exists? 1 + wait + assert_equal 1, @logger.logged(:debug).size + assert_match(/Developer Exists/, @logger.logged(:debug).last) + assert_match(/SELECT .*?FROM .?developers.?/i, @logger.logged(:debug).last) + end + def test_cached_queries ActiveRecord::Base.cache do Developer.all diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb index a41ce539c7..ef35f3341e 100644 --- a/activerecord/test/cases/mass_assignment_security_test.rb +++ b/activerecord/test/cases/mass_assignment_security_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/company' require 'models/subscriber' require 'models/keyboard' @@ -384,81 +384,81 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase # build - def test_has_one_build_with_attr_protected_attributes + def test_belongs_to_build_with_attr_protected_attributes best_friend = @person.build_best_friend_of(attributes_hash) assert_default_attributes(best_friend) end - def test_has_one_build_with_attr_accessible_attributes + def test_belongs_to_build_with_attr_accessible_attributes best_friend = @person.build_best_friend_of(attributes_hash) assert_default_attributes(best_friend) end - def test_has_one_build_with_admin_role_with_attr_protected_attributes + def test_belongs_to_build_with_admin_role_with_attr_protected_attributes best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin) assert_admin_attributes(best_friend) end - def test_has_one_build_with_admin_role_with_attr_accessible_attributes + def test_belongs_to_build_with_admin_role_with_attr_accessible_attributes best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin) assert_admin_attributes(best_friend) end - def test_has_one_build_without_protection + def test_belongs_to_build_without_protection best_friend = @person.build_best_friend_of(attributes_hash, :without_protection => true) assert_all_attributes(best_friend) end # create - def test_has_one_create_with_attr_protected_attributes + def test_belongs_to_create_with_attr_protected_attributes best_friend = @person.create_best_friend_of(attributes_hash) assert_default_attributes(best_friend, true) end - def test_has_one_create_with_attr_accessible_attributes + def test_belongs_to_create_with_attr_accessible_attributes best_friend = @person.create_best_friend_of(attributes_hash) assert_default_attributes(best_friend, true) end - def test_has_one_create_with_admin_role_with_attr_protected_attributes + def test_belongs_to_create_with_admin_role_with_attr_protected_attributes best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_admin_role_with_attr_accessible_attributes + def test_belongs_to_create_with_admin_role_with_attr_accessible_attributes best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_without_protection + def test_belongs_to_create_without_protection best_friend = @person.create_best_friend_of(attributes_hash, :without_protection => true) assert_all_attributes(best_friend) end # create! - def test_has_one_create_with_bang_with_attr_protected_attributes + def test_belongs_to_create_with_bang_with_attr_protected_attributes best_friend = @person.create_best_friend!(attributes_hash) assert_default_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_attr_accessible_attributes + def test_belongs_to_create_with_bang_with_attr_accessible_attributes best_friend = @person.create_best_friend!(attributes_hash) assert_default_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes + def test_belongs_to_create_with_bang_with_admin_role_with_attr_protected_attributes best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes + def test_belongs_to_create_with_bang_with_admin_role_with_attr_accessible_attributes best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_bang_without_protection + def test_belongs_to_create_with_bang_without_protection best_friend = @person.create_best_friend!(attributes_hash, :without_protection => true) assert_all_attributes(best_friend) end @@ -472,83 +472,328 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase # build - def test_has_one_build_with_attr_protected_attributes + def test_has_many_build_with_attr_protected_attributes best_friend = @person.best_friends.build(attributes_hash) assert_default_attributes(best_friend) end - def test_has_one_build_with_attr_accessible_attributes + def test_has_many_build_with_attr_accessible_attributes best_friend = @person.best_friends.build(attributes_hash) assert_default_attributes(best_friend) end - def test_has_one_build_with_admin_role_with_attr_protected_attributes + def test_has_many_build_with_admin_role_with_attr_protected_attributes best_friend = @person.best_friends.build(attributes_hash, :as => :admin) assert_admin_attributes(best_friend) end - def test_has_one_build_with_admin_role_with_attr_accessible_attributes + def test_has_many_build_with_admin_role_with_attr_accessible_attributes best_friend = @person.best_friends.build(attributes_hash, :as => :admin) assert_admin_attributes(best_friend) end - def test_has_one_build_without_protection + def test_has_many_build_without_protection best_friend = @person.best_friends.build(attributes_hash, :without_protection => true) assert_all_attributes(best_friend) end # create - def test_has_one_create_with_attr_protected_attributes + def test_has_many_create_with_attr_protected_attributes best_friend = @person.best_friends.create(attributes_hash) assert_default_attributes(best_friend, true) end - def test_has_one_create_with_attr_accessible_attributes + def test_has_many_create_with_attr_accessible_attributes best_friend = @person.best_friends.create(attributes_hash) assert_default_attributes(best_friend, true) end - def test_has_one_create_with_admin_role_with_attr_protected_attributes + def test_has_many_create_with_admin_role_with_attr_protected_attributes best_friend = @person.best_friends.create(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_admin_role_with_attr_accessible_attributes + def test_has_many_create_with_admin_role_with_attr_accessible_attributes best_friend = @person.best_friends.create(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_without_protection + def test_has_many_create_without_protection best_friend = @person.best_friends.create(attributes_hash, :without_protection => true) assert_all_attributes(best_friend) end # create! - def test_has_one_create_with_bang_with_attr_protected_attributes + def test_has_many_create_with_bang_with_attr_protected_attributes best_friend = @person.best_friends.create!(attributes_hash) assert_default_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_attr_accessible_attributes + def test_has_many_create_with_bang_with_attr_accessible_attributes best_friend = @person.best_friends.create!(attributes_hash) assert_default_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes + def test_has_many_create_with_bang_with_admin_role_with_attr_protected_attributes best_friend = @person.best_friends.create!(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes + def test_has_many_create_with_bang_with_admin_role_with_attr_accessible_attributes best_friend = @person.best_friends.create!(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_bang_without_protection + def test_has_many_create_with_bang_without_protection best_friend = @person.best_friends.create!(attributes_hash, :without_protection => true) assert_all_attributes(best_friend) end end + + +class MassAssignmentSecurityNestedAttributesTest < ActiveRecord::TestCase + include MassAssignmentTestHelpers + + def nested_attributes_hash(association, collection = false, except = [:id]) + if collection + { :first_name => 'David' }.merge(:"#{association}_attributes" => [attributes_hash.except(*except)]) + else + { :first_name => 'David' }.merge(:"#{association}_attributes" => attributes_hash.except(*except)) + end + end + + # build + + def test_has_one_new_with_attr_protected_attributes + person = LoosePerson.new(nested_attributes_hash(:best_friend)) + assert_default_attributes(person.best_friend) + end + + def test_has_one_new_with_attr_accessible_attributes + person = TightPerson.new(nested_attributes_hash(:best_friend)) + assert_default_attributes(person.best_friend) + end + + def test_has_one_new_with_admin_role_with_attr_protected_attributes + person = LoosePerson.new(nested_attributes_hash(:best_friend), :as => :admin) + assert_admin_attributes(person.best_friend) + end + + def test_has_one_new_with_admin_role_with_attr_accessible_attributes + person = TightPerson.new(nested_attributes_hash(:best_friend), :as => :admin) + assert_admin_attributes(person.best_friend) + end + + def test_has_one_new_without_protection + person = LoosePerson.new(nested_attributes_hash(:best_friend, false, nil), :without_protection => true) + assert_all_attributes(person.best_friend) + end + + def test_belongs_to_new_with_attr_protected_attributes + person = LoosePerson.new(nested_attributes_hash(:best_friend_of)) + assert_default_attributes(person.best_friend_of) + end + + def test_belongs_to_new_with_attr_accessible_attributes + person = TightPerson.new(nested_attributes_hash(:best_friend_of)) + assert_default_attributes(person.best_friend_of) + end + + def test_belongs_to_new_with_admin_role_with_attr_protected_attributes + person = LoosePerson.new(nested_attributes_hash(:best_friend_of), :as => :admin) + assert_admin_attributes(person.best_friend_of) + end + + def test_belongs_to_new_with_admin_role_with_attr_accessible_attributes + person = TightPerson.new(nested_attributes_hash(:best_friend_of), :as => :admin) + assert_admin_attributes(person.best_friend_of) + end + + def test_belongs_to_new_without_protection + person = LoosePerson.new(nested_attributes_hash(:best_friend_of, false, nil), :without_protection => true) + assert_all_attributes(person.best_friend_of) + end + + def test_has_many_new_with_attr_protected_attributes + person = LoosePerson.new(nested_attributes_hash(:best_friends, true)) + assert_default_attributes(person.best_friends.first) + end + + def test_has_many_new_with_attr_accessible_attributes + person = TightPerson.new(nested_attributes_hash(:best_friends, true)) + assert_default_attributes(person.best_friends.first) + end + + def test_has_many_new_with_admin_role_with_attr_protected_attributes + person = LoosePerson.new(nested_attributes_hash(:best_friends, true), :as => :admin) + assert_admin_attributes(person.best_friends.first) + end + + def test_has_many_new_with_admin_role_with_attr_accessible_attributes + person = TightPerson.new(nested_attributes_hash(:best_friends, true), :as => :admin) + assert_admin_attributes(person.best_friends.first) + end + + def test_has_many_new_without_protection + person = LoosePerson.new(nested_attributes_hash(:best_friends, true, nil), :without_protection => true) + assert_all_attributes(person.best_friends.first) + end + + # create + + def test_has_one_create_with_attr_protected_attributes + person = LoosePerson.create(nested_attributes_hash(:best_friend)) + assert_default_attributes(person.best_friend, true) + end + + def test_has_one_create_with_attr_accessible_attributes + person = TightPerson.create(nested_attributes_hash(:best_friend)) + assert_default_attributes(person.best_friend, true) + end + + def test_has_one_create_with_admin_role_with_attr_protected_attributes + person = LoosePerson.create(nested_attributes_hash(:best_friend), :as => :admin) + assert_admin_attributes(person.best_friend, true) + end + + def test_has_one_create_with_admin_role_with_attr_accessible_attributes + person = TightPerson.create(nested_attributes_hash(:best_friend), :as => :admin) + assert_admin_attributes(person.best_friend, true) + end + + def test_has_one_create_without_protection + person = LoosePerson.create(nested_attributes_hash(:best_friend, false, nil), :without_protection => true) + assert_all_attributes(person.best_friend) + end + + def test_belongs_to_create_with_attr_protected_attributes + person = LoosePerson.create(nested_attributes_hash(:best_friend_of)) + assert_default_attributes(person.best_friend_of, true) + end + + def test_belongs_to_create_with_attr_accessible_attributes + person = TightPerson.create(nested_attributes_hash(:best_friend_of)) + assert_default_attributes(person.best_friend_of, true) + end + + def test_belongs_to_create_with_admin_role_with_attr_protected_attributes + person = LoosePerson.create(nested_attributes_hash(:best_friend_of), :as => :admin) + assert_admin_attributes(person.best_friend_of, true) + end + + def test_belongs_to_create_with_admin_role_with_attr_accessible_attributes + person = TightPerson.create(nested_attributes_hash(:best_friend_of), :as => :admin) + assert_admin_attributes(person.best_friend_of, true) + end + + def test_belongs_to_create_without_protection + person = LoosePerson.create(nested_attributes_hash(:best_friend_of, false, nil), :without_protection => true) + assert_all_attributes(person.best_friend_of) + end + + def test_has_many_create_with_attr_protected_attributes + person = LoosePerson.create(nested_attributes_hash(:best_friends, true)) + assert_default_attributes(person.best_friends.first, true) + end + + def test_has_many_create_with_attr_accessible_attributes + person = TightPerson.create(nested_attributes_hash(:best_friends, true)) + assert_default_attributes(person.best_friends.first, true) + end + + def test_has_many_create_with_admin_role_with_attr_protected_attributes + person = LoosePerson.create(nested_attributes_hash(:best_friends, true), :as => :admin) + assert_admin_attributes(person.best_friends.first, true) + end + + def test_has_many_create_with_admin_role_with_attr_accessible_attributes + person = TightPerson.create(nested_attributes_hash(:best_friends, true), :as => :admin) + assert_admin_attributes(person.best_friends.first, true) + end + + def test_has_many_create_without_protection + person = LoosePerson.create(nested_attributes_hash(:best_friends, true, nil), :without_protection => true) + assert_all_attributes(person.best_friends.first) + end + + # create! + + def test_has_one_create_with_bang_with_attr_protected_attributes + person = LoosePerson.create!(nested_attributes_hash(:best_friend)) + assert_default_attributes(person.best_friend, true) + end + + def test_has_one_create_with_bang_with_attr_accessible_attributes + person = TightPerson.create!(nested_attributes_hash(:best_friend)) + assert_default_attributes(person.best_friend, true) + end + + def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes + person = LoosePerson.create!(nested_attributes_hash(:best_friend), :as => :admin) + assert_admin_attributes(person.best_friend, true) + end + + def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes + person = TightPerson.create!(nested_attributes_hash(:best_friend), :as => :admin) + assert_admin_attributes(person.best_friend, true) + end + + def test_has_one_create_with_bang_without_protection + person = LoosePerson.create!(nested_attributes_hash(:best_friend, false, nil), :without_protection => true) + assert_all_attributes(person.best_friend) + end + + def test_belongs_to_create_with_bang_with_attr_protected_attributes + person = LoosePerson.create!(nested_attributes_hash(:best_friend_of)) + assert_default_attributes(person.best_friend_of, true) + end + + def test_belongs_to_create_with_bang_with_attr_accessible_attributes + person = TightPerson.create!(nested_attributes_hash(:best_friend_of)) + assert_default_attributes(person.best_friend_of, true) + end + + def test_belongs_to_create_with_bang_with_admin_role_with_attr_protected_attributes + person = LoosePerson.create!(nested_attributes_hash(:best_friend_of), :as => :admin) + assert_admin_attributes(person.best_friend_of, true) + end + + def test_belongs_to_create_with_bang_with_admin_role_with_attr_accessible_attributes + person = TightPerson.create!(nested_attributes_hash(:best_friend_of), :as => :admin) + assert_admin_attributes(person.best_friend_of, true) + end + + def test_belongs_to_create_with_bang_without_protection + person = LoosePerson.create!(nested_attributes_hash(:best_friend_of, false, nil), :without_protection => true) + assert_all_attributes(person.best_friend_of) + end + + def test_has_many_create_with_bang_with_attr_protected_attributes + person = LoosePerson.create!(nested_attributes_hash(:best_friends, true)) + assert_default_attributes(person.best_friends.first, true) + end + + def test_has_many_create_with_bang_with_attr_accessible_attributes + person = TightPerson.create!(nested_attributes_hash(:best_friends, true)) + assert_default_attributes(person.best_friends.first, true) + end + + def test_has_many_create_with_bang_with_admin_role_with_attr_protected_attributes + person = LoosePerson.create!(nested_attributes_hash(:best_friends, true), :as => :admin) + assert_admin_attributes(person.best_friends.first, true) + end + + def test_has_many_create_with_bang_with_admin_role_with_attr_accessible_attributes + person = TightPerson.create!(nested_attributes_hash(:best_friends, true), :as => :admin) + assert_admin_attributes(person.best_friends.first, true) + end + + def test_has_many_create_with_bang_without_protection + person = LoosePerson.create!(nested_attributes_hash(:best_friends, true, nil), :without_protection => true) + assert_all_attributes(person.best_friends.first) + end + +end diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index 52e76cb91a..a0cb5dbdc5 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -2,7 +2,7 @@ # All the tests were already ported to relation_scoping_test.rb when the new # relation scoping API was added. -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/post' require 'models/author' require 'models/developer' diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb index 986dc7173d..36007255fa 100644 --- a/activerecord/test/cases/migration/command_recorder_test.rb +++ b/activerecord/test/cases/migration/command_recorder_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require "cases/helper" module ActiveRecord class Migration @@ -29,7 +29,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 01f7575653..93a1249e43 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'bigdecimal/util' require 'models/person' @@ -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/mixin_test.rb b/activerecord/test/cases/mixin_test.rb index 65101199b8..f927c13979 100644 --- a/activerecord/test/cases/mixin_test.rb +++ b/activerecord/test/cases/mixin_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" class Mixin < ActiveRecord::Base end diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb index df5e66ea3c..a2041af16a 100644 --- a/activerecord/test/cases/modules_test.rb +++ b/activerecord/test/cases/modules_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/company_in_module' require 'models/shop' diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb index fca832e50b..bd51388e05 100644 --- a/activerecord/test/cases/multiple_db_test.rb +++ b/activerecord/test/cases/multiple_db_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/entrant' require 'models/bird' diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 2403645940..2afe3c8f32 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'active_support/core_ext/array/random_access' require 'models/post' require 'models/topic' diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index d94d4768a9..6568eb1d18 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require "models/pirate" require "models/ship" require "models/ship_part" diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 00543f7350..57d1441128 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/post' require 'models/comment' require 'models/author' diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb index 0321c5208f..379cf5b44e 100644 --- a/activerecord/test/cases/pooled_connections_test.rb +++ b/activerecord/test/cases/pooled_connections_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require "models/project" require "timeout" diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index e810a155a0..7e3da145e5 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/topic' require 'models/reply' require 'models/subscriber' @@ -26,7 +26,7 @@ class PrimaryKeysTest < ActiveRecord::TestCase def test_to_key_with_primary_key_after_destroy topic = Topic.find(1) topic.destroy - assert_equal [1], topic.to_key + assert_equal nil, topic.to_key end def test_integer_key diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index b08443b455..ad17f6f83a 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/topic' require 'models/task' require 'models/category' @@ -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/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index 4cb38118c9..80ee74e41e 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" module ActiveRecord module ConnectionAdapters diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb index 88168776b1..e21109baae 100644 --- a/activerecord/test/cases/readonly_test.rb +++ b/activerecord/test/cases/readonly_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/post' require 'models/comment' require 'models/developer' diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index c7288baaba..41312e8661 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/topic' require 'models/customer' require 'models/company' @@ -76,7 +76,7 @@ class ReflectionTest < ActiveRecord::TestCase end def test_reflection_klass_for_nested_class_name - reflection = MacroReflection.new(nil, nil, { :class_name => 'MyApplication::Business::Company' }, nil) + reflection = MacroReflection.new(:company, nil, { :class_name => 'MyApplication::Business::Company' }, ActiveRecord::Base) assert_nothing_raised do assert_equal MyApplication::Business::Company, reflection.klass end diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index 5067384fb6..f2d177d834 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/post' require 'models/author' require 'models/developer' @@ -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 5ad089c875..b23ead6feb 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/post' require 'models/comment' @@ -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 ecc68b19d4..0aaa0342b1 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/tag' require 'models/tagging' require 'models/post' @@ -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 @@ -372,6 +391,15 @@ class RelationTest < ActiveRecord::TestCase assert_equal Post.find(1).last_comment, post.last_comment end + def test_dynamic_find_by_attributes_should_yield_found_object + david = authors(:david) + yielded_value = nil + Author.find_by_name(david.name) do |author| + yielded_value = author + end + assert_equal david, yielded_value + end + def test_dynamic_find_by_attributes david = authors(:david) author = Author.preload(:taggings).find_by_id(david.id) diff --git a/activerecord/test/cases/reload_models_test.rb b/activerecord/test/cases/reload_models_test.rb index 0ac46a2768..0d16a3526f 100644 --- a/activerecord/test/cases/reload_models_test.rb +++ b/activerecord/test/cases/reload_models_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/owner' require 'models/pet' diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb index 7c30bcf4c8..817897ceac 100644 --- a/activerecord/test/cases/sanitize_test.rb +++ b/activerecord/test/cases/sanitize_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/binary' class SanitizeTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index e765fdfe70..4adecf8e83 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'stringio' diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb index 9c976f4999..677d659f39 100644 --- a/activerecord/test/cases/serialization_test.rb +++ b/activerecord/test/cases/serialization_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/contact' require 'models/topic' require 'models/reply' diff --git a/activerecord/test/cases/session_store/session_test.rb b/activerecord/test/cases/session_store/session_test.rb index fb6f4cfd44..669c0b7b4d 100644 --- a/activerecord/test/cases/session_store/session_test.rb +++ b/activerecord/test/cases/session_store/session_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require 'cases/helper' require 'action_dispatch' require 'active_record/session_store' diff --git a/activerecord/test/cases/session_store/sql_bypass.rb b/activerecord/test/cases/session_store/sql_bypass.rb index 97ccb1f5c9..7402b2afd6 100644 --- a/activerecord/test/cases/session_store/sql_bypass.rb +++ b/activerecord/test/cases/session_store/sql_bypass.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require 'cases/helper' require 'action_dispatch' require 'active_record/session_store' diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index ce6419c0da..4445a12e1d 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require 'cases/helper' require 'models/developer' require 'models/owner' require 'models/pet' @@ -11,6 +11,7 @@ class TimestampTest < ActiveRecord::TestCase def setup @developer = Developer.first + @developer.update_attribute(:updated_at, Time.now.prev_month) @previously_updated_at = @developer.updated_at end @@ -40,6 +41,15 @@ class TimestampTest < ActiveRecord::TestCase assert_equal previous_salary, @developer.salary end + def test_touching_a_record_with_default_scope_that_excludes_it_updates_its_timestamp + developer = @developer.becomes(DeveloperCalledJamis) + + developer.touch + assert_not_equal @previously_updated_at, developer.updated_at + developer.reload + assert_not_equal @previously_updated_at, developer.updated_at + end + def test_saving_when_record_timestamps_is_false_doesnt_update_its_timestamp Developer.record_timestamps = false @developer.name = "John Smith" diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 2d88e660b3..85f222bca2 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/topic' class TransactionCallbacksTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index fa33ffaed3..110a18772f 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/topic' require 'models/reply' require 'models/developer' diff --git a/activerecord/test/cases/unconnected_test.rb b/activerecord/test/cases/unconnected_test.rb index a6aaf1eb3d..f85fb4e5da 100644 --- a/activerecord/test/cases/unconnected_test.rb +++ b/activerecord/test/cases/unconnected_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" class TestRecord < ActiveRecord::Base end diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb index 45290680f9..56e345990f 100644 --- a/activerecord/test/cases/validations/association_validation_test.rb +++ b/activerecord/test/cases/validations/association_validation_test.rb @@ -1,5 +1,5 @@ # encoding: utf-8 -require File.expand_path('../../helper', __FILE__) +require "cases/helper" require 'models/topic' require 'models/reply' require 'models/owner' diff --git a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb index 922fb5dce7..628029f8df 100644 --- a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require "cases/helper" require 'models/topic' class I18nGenerateMessageValidationTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb index 46f0c3c49d..15b97c02c8 100644 --- a/activerecord/test/cases/validations/i18n_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_validation_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../helper', __FILE__) +require "cases/helper" require 'models/topic' require 'models/reply' diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index 218f673451..0f1b3667cc 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -1,5 +1,5 @@ # encoding: utf-8 -require File.expand_path('../../helper', __FILE__) +require "cases/helper" require 'models/topic' require 'models/reply' require 'models/warehouse_thing' diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index 8764a22d40..c3e494866b 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -1,5 +1,5 @@ # encoding: utf-8 -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/topic' require 'models/reply' require 'models/person' diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb index c48957e7a7..756c8a32eb 100644 --- a/activerecord/test/cases/xml_serialization_test.rb +++ b/activerecord/test/cases/xml_serialization_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/contact' require 'models/post' require 'models/author' diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb index 6caa782a97..0b54c309d1 100644 --- a/activerecord/test/cases/yaml_serialization_test.rb +++ b/activerecord/test/cases/yaml_serialization_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../helper', __FILE__) +require "cases/helper" require 'models/topic' class YamlSerializationTest < ActiveRecord::TestCase diff --git a/activerecord/test/config.example.yml b/activerecord/test/config.example.yml index 6ed38db66d..8c1a45430e 100644 --- a/activerecord/test/config.example.yml +++ b/activerecord/test/config.example.yml @@ -92,18 +92,18 @@ connections: username: admin oracle: - arunit: - adapter: oracle_enhanced - database: <%= ENV['ARUNIT_DB_NAME'] || 'orcl' %> - username: arunit - password: arunit - emulate_oracle_adapter: true - arunit: - adapter: oracle_enhanced - database: <%= ENV['ARUNIT_DB_NAME'] || 'orcl' %> - username: arunit2 - password: arunit2 - emulate_oracle_adapter: true + arunit: + adapter: oracle_enhanced + database: <%= ENV['ARUNIT_DB_NAME'] || 'orcl' %> + username: <%= ENV['ARUNIT_USER_NAME'] || 'arunit' %> + password: <%= ENV['ARUNIT_PASSWORD'] || 'arunit' %> + emulate_oracle_adapter: true + arunit2: + adapter: oracle_enhanced + database: <%= ENV['ARUNIT_DB_NAME'] || 'orcl' %> + username: <%= ENV['ARUNIT2_USER_NAME'] || 'arunit2' %> + password: <%= ENV['ARUNIT2_PASSWORD'] || 'arunit2' %> + emulate_oracle_adapter: true postgresql: arunit: diff --git a/activerecord/test/connections/db2.rb b/activerecord/test/connections/db2.rb deleted file mode 100644 index f43b3fda57..0000000000 --- a/activerecord/test/connections/db2.rb +++ /dev/null @@ -1 +0,0 @@ -ENV['ARCONN'] = 'db2' diff --git a/activerecord/test/connections/firebird.rb b/activerecord/test/connections/firebird.rb deleted file mode 100644 index fa874c0bb3..0000000000 --- a/activerecord/test/connections/firebird.rb +++ /dev/null @@ -1 +0,0 @@ -ENV['ARCONN'] = 'firebird' diff --git a/activerecord/test/connections/frontbase.rb b/activerecord/test/connections/frontbase.rb deleted file mode 100644 index 9d1d5597d4..0000000000 --- a/activerecord/test/connections/frontbase.rb +++ /dev/null @@ -1 +0,0 @@ -ENV['ARCONN'] = 'frontbase' diff --git a/activerecord/test/connections/jdbcderby.rb b/activerecord/test/connections/jdbcderby.rb deleted file mode 100644 index 1c7b63ccdc..0000000000 --- a/activerecord/test/connections/jdbcderby.rb +++ /dev/null @@ -1 +0,0 @@ -ENV['ARCONN'] = 'jdbcderby' diff --git a/activerecord/test/connections/jdbch2.rb b/activerecord/test/connections/jdbch2.rb deleted file mode 100644 index d2fb177ae3..0000000000 --- a/activerecord/test/connections/jdbch2.rb +++ /dev/null @@ -1 +0,0 @@ -ENV['ARCONN'] = 'jdbch2' diff --git a/activerecord/test/connections/jdbchsqldb.rb b/activerecord/test/connections/jdbchsqldb.rb deleted file mode 100644 index 22e6d8729e..0000000000 --- a/activerecord/test/connections/jdbchsqldb.rb +++ /dev/null @@ -1 +0,0 @@ -ENV['ARCONN'] = 'jdbchsqldb' diff --git a/activerecord/test/connections/jdbcmysql.rb b/activerecord/test/connections/jdbcmysql.rb deleted file mode 100644 index f1aaad93a3..0000000000 --- a/activerecord/test/connections/jdbcmysql.rb +++ /dev/null @@ -1 +0,0 @@ -ENV['ARCONN'] = 'jdbcmysql' diff --git a/activerecord/test/connections/jdbcpostgresql.rb b/activerecord/test/connections/jdbcpostgresql.rb deleted file mode 100644 index 6809115f36..0000000000 --- a/activerecord/test/connections/jdbcpostgresql.rb +++ /dev/null @@ -1 +0,0 @@ -ENV['ARCONN'] = 'jdbcpostgresql' diff --git a/activerecord/test/connections/jdbcsqlite3.rb b/activerecord/test/connections/jdbcsqlite3.rb deleted file mode 100644 index 9cc872905a..0000000000 --- a/activerecord/test/connections/jdbcsqlite3.rb +++ /dev/null @@ -1 +0,0 @@ -ENV['ARCONN'] = 'jdbcsqlite3' diff --git a/activerecord/test/connections/mysql.rb b/activerecord/test/connections/mysql.rb deleted file mode 100644 index 2f30603098..0000000000 --- a/activerecord/test/connections/mysql.rb +++ /dev/null @@ -1 +0,0 @@ -ENV['ARCONN'] = 'mysql' diff --git a/activerecord/test/connections/mysql2.rb b/activerecord/test/connections/mysql2.rb deleted file mode 100644 index 10aa9171bd..0000000000 --- a/activerecord/test/connections/mysql2.rb +++ /dev/null @@ -1 +0,0 @@ -ENV['ARCONN'] = 'mysql2' diff --git a/activerecord/test/connections/openbase.rb b/activerecord/test/connections/openbase.rb deleted file mode 100644 index bf072579f4..0000000000 --- a/activerecord/test/connections/openbase.rb +++ /dev/null @@ -1 +0,0 @@ -ENV['ARCONN'] = 'openbase' diff --git a/activerecord/test/connections/oracle.rb b/activerecord/test/connections/oracle.rb deleted file mode 100644 index dc64f7c5fc..0000000000 --- a/activerecord/test/connections/oracle.rb +++ /dev/null @@ -1,7 +0,0 @@ -ENV['ARCONN'] = 'oracle' - -# uses oracle_enhanced adapter in ENV['ORACLE_ENHANCED_PATH'] or from github.com/rsim/oracle-enhanced.git -require 'active_record/connection_adapters/oracle_enhanced_adapter' - -# otherwise failed with silence_warnings method missing exception -require 'active_support/core_ext/kernel/reporting' diff --git a/activerecord/test/connections/postgresql.rb b/activerecord/test/connections/postgresql.rb deleted file mode 100644 index b8bfb6e846..0000000000 --- a/activerecord/test/connections/postgresql.rb +++ /dev/null @@ -1 +0,0 @@ -ENV['ARCONN'] = 'postgresql' diff --git a/activerecord/test/connections/sqlite3.rb b/activerecord/test/connections/sqlite3.rb deleted file mode 100644 index e5c75657a5..0000000000 --- a/activerecord/test/connections/sqlite3.rb +++ /dev/null @@ -1 +0,0 @@ -ENV['ARCONN'] = 'sqlite3' diff --git a/activerecord/test/connections/sqlite3_mem.rb b/activerecord/test/connections/sqlite3_mem.rb deleted file mode 100644 index 900a5c7947..0000000000 --- a/activerecord/test/connections/sqlite3_mem.rb +++ /dev/null @@ -1 +0,0 @@ -ENV['ARCONN'] = 'sqlite3_mem' diff --git a/activerecord/test/connections/sybase.rb b/activerecord/test/connections/sybase.rb deleted file mode 100644 index 7decdf7f39..0000000000 --- a/activerecord/test/connections/sybase.rb +++ /dev/null @@ -1 +0,0 @@ -ENV['ARCONN'] = 'sybase' diff --git a/activerecord/test/fixtures/pirates.yml b/activerecord/test/fixtures/pirates.yml index abb91101da..a47d894249 100644 --- a/activerecord/test/fixtures/pirates.yml +++ b/activerecord/test/fixtures/pirates.yml @@ -5,5 +5,5 @@ blackbeard: redbeard: catchphrase: "Avast!" parrot: louis - created_on: <%= 2.weeks.ago.to_s(:db) %> - updated_on: <%= 2.weeks.ago.to_s(:db) %> + created_on: <%= 2.weeks.ago.utc.to_s(:db) %> + updated_on: <%= 2.weeks.ago.utc.to_s(:db) %> diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb index 0dcc8d5970..efb98b66e7 100644 --- a/activerecord/test/models/bulb.rb +++ b/activerecord/test/models/bulb.rb @@ -4,13 +4,18 @@ class Bulb < ActiveRecord::Base attr_protected :car_id, :frickinawesome - attr_reader :scope_after_initialize + attr_reader :scope_after_initialize, :attributes_after_initialize after_initialize :record_scope_after_initialize def record_scope_after_initialize @scope_after_initialize = self.class.scoped end + after_initialize :record_attributes_after_initialize + def record_attributes_after_initialize + @attributes_after_initialize = attributes.dup + end + def color=(color) self[:color] = color.upcase + "!" end @@ -28,4 +33,4 @@ class Bulb < ActiveRecord::Base end class CustomBulb < Bulb -end
\ No newline at end of file +end diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb index 43650c0427..88b139d931 100644 --- a/activerecord/test/models/comment.rb +++ b/activerecord/test/models/comment.rb @@ -7,10 +7,13 @@ class Comment < ActiveRecord::Base :joins => :post, :conditions => { "posts.author_id" => 1 } scope :created - + belongs_to :post, :counter_cache => true has_many :ratings + has_many :children, :class_name => 'Comment', :foreign_key => :parent_id + belongs_to :parent, :class_name => 'Comment', :counter_cache => :children_count + def self.what_are_you 'a comment...' end diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index e0b30efd51..c1f7a4171a 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -124,6 +124,18 @@ class Client < Company has_many :accounts, :through => :firm belongs_to :account + class RaisedOnSave < RuntimeError; end + attr_accessor :raise_on_save + before_save do + raise RaisedOnSave if raise_on_save + end + + class RaisedOnDestroy < RuntimeError; end + attr_accessor :raise_on_destroy + before_destroy do + raise RaisedOnDestroy if raise_on_destroy + end + # Record destruction so we can test whether firm.clients.clear has # is calling client.destroy, deleting from the database, or setting # foreign keys to NULL. 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/models/person.rb b/activerecord/test/models/person.rb index a58c9bf572..967a3625aa 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -59,8 +59,9 @@ class LoosePerson < ActiveRecord::Base has_one :best_friend, :class_name => 'LoosePerson', :foreign_key => :best_friend_id belongs_to :best_friend_of, :class_name => 'LoosePerson', :foreign_key => :best_friend_of_id - has_many :best_friends, :class_name => 'LoosePerson', :foreign_key => :best_friend_id + + accepts_nested_attributes_for :best_friend, :best_friend_of, :best_friends end class LooseDescendant < LoosePerson; end @@ -70,11 +71,14 @@ class TightPerson < ActiveRecord::Base attr_accessible :first_name, :gender attr_accessible :first_name, :gender, :comments, :as => :admin + attr_accessible :best_friend_attributes, :best_friend_of_attributes, :best_friends_attributes + attr_accessible :best_friend_attributes, :best_friend_of_attributes, :best_friends_attributes, :as => :admin has_one :best_friend, :class_name => 'TightPerson', :foreign_key => :best_friend_id belongs_to :best_friend_of, :class_name => 'TightPerson', :foreign_key => :best_friend_of_id - has_many :best_friends, :class_name => 'TightPerson', :foreign_key => :best_friend_id + + accepts_nested_attributes_for :best_friend, :best_friend_of, :best_friends end class TightDescendant < TightPerson; end
\ No newline at end of file diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 4fe311b441..64e0452100 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -155,6 +155,7 @@ ActiveRecord::Schema.define do end t.string :type t.integer :taggings_count, :default => 0 + t.integer :children_count, :default => 0 end create_table :companies, :force => true do |t| @@ -305,6 +306,8 @@ ActiveRecord::Schema.define do t.references :student end + create_table :lint_models, :force => true + create_table :line_items, :force => true do |t| t.integer :invoice_id t.integer :amount @@ -719,6 +722,8 @@ ActiveRecord::Schema.define do end execute "ALTER TABLE fk_test_has_fk ADD CONSTRAINT fk_name FOREIGN KEY (#{quote_column_name 'fk_id'}) REFERENCES #{quote_table_name 'fk_test_has_pk'} (#{quote_column_name 'id'})" + + execute "ALTER TABLE lessons_students ADD CONSTRAINT student_id_fk FOREIGN KEY (#{quote_column_name 'student_id'}) REFERENCES #{quote_table_name 'students'} (#{quote_column_name 'id'})" end end diff --git a/activerecord/test/support/config.rb b/activerecord/test/support/config.rb index 3b1598e368..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 @@ -10,13 +11,16 @@ module ARTest private + def config_file + Pathname.new(ENV['ARCONFIG'] || TEST_ROOT + '/config.yml') + end + def read_config - unless File.exist?(TEST_ROOT + '/config.yml') - FileUtils.cp TEST_ROOT + '/config.example.yml', TEST_ROOT + '/config.yml' + unless config_file.exist? + FileUtils.cp TEST_ROOT + '/config.example.yml', config_file end - raw = File.read(TEST_ROOT + '/config.yml') - erb = Erubis::Eruby.new(raw) + erb = Erubis::Eruby.new(config_file.read) expand_config(YAML.parse(erb.result(binding)).transform) end diff --git a/activerecord/test/support/connection.rb b/activerecord/test/support/connection.rb index fcd2e4dee5..a39794fa39 100644 --- a/activerecord/test/support/connection.rb +++ b/activerecord/test/support/connection.rb @@ -11,7 +11,7 @@ module ARTest end def self.connect - puts "Using #{connection_name}" + puts "Using #{connection_name} with Identity Map #{ActiveRecord::IdentityMap.enabled? ? 'on' : 'off'}" ActiveRecord::Base.logger = Logger.new("debug.log") ActiveRecord::Base.configurations = connection_config ActiveRecord::Base.establish_connection 'arunit' diff --git a/activeresource/lib/active_resource/schema.rb b/activeresource/lib/active_resource/schema.rb index 3fd37a9bb6..5957969aa2 100644 --- a/activeresource/lib/active_resource/schema.rb +++ b/activeresource/lib/active_resource/schema.rb @@ -20,8 +20,8 @@ module ActiveResource # :nodoc: # end # # The schema stores the name and type of each attribute. That is then - # read out by the schema method to populate the actual - # Resource's schema + # read out by the schema method to populate the schema of the actual + # resource. def initialize @attrs = {} end @@ -40,6 +40,12 @@ module ActiveResource # :nodoc: # The following are the attribute types supported by Active Resource # migrations. KNOWN_ATTRIBUTE_TYPES.each do |attr_type| + # def string(*args) + # options = args.extract_options! + # attr_names = args + # + # attr_names.each { |name| attribute(name, 'string', options) } + # end class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{attr_type.to_s}(*args) options = args.extract_options! diff --git a/activeresource/lib/active_resource/version.rb b/activeresource/lib/active_resource/version.rb index 440b504344..d53374b261 100644 --- a/activeresource/lib/active_resource/version.rb +++ b/activeresource/lib/active_resource/version.rb @@ -1,9 +1,9 @@ module ActiveResource module VERSION #:nodoc: MAJOR = 3 - MINOR = 1 + MINOR = 2 TINY = 0 - PRE = "rc1" + PRE = "beta" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/activeresource/test/cases/base/load_test.rb b/activeresource/test/cases/base/load_test.rb index d6b04cfaa8..0d030148d0 100644 --- a/activeresource/test/cases/base/load_test.rb +++ b/activeresource/test/cases/base/load_test.rb @@ -72,7 +72,6 @@ class BaseLoadTest < Test::Unit::TestCase def test_after_load_attributes_are_accessible_via_indifferent_access assert_equal Hash.new, @person.attributes - matz_attributes = @matz.values.first assert_equal @matz.stringify_keys, @person.load(@matz).attributes assert_equal @matz[:name], @person.attributes['name'] assert_equal @matz[:name], @person.attributes[:name] diff --git a/activeresource/test/cases/finder_test.rb b/activeresource/test/cases/finder_test.rb index 9c51f2a390..5fbbfeef6e 100644 --- a/activeresource/test/cases/finder_test.rb +++ b/activeresource/test/cases/finder_test.rb @@ -95,7 +95,7 @@ class FinderTest < Test::Unit::TestCase def test_find_all_sub_objects_not_found assert_nothing_raised do - addys = StreetAddress.find(:all, :params => { :person_id => 2 }) + StreetAddress.find(:all, :params => { :person_id => 2 }) end end diff --git a/activeresource/test/cases/log_subscriber_test.rb b/activeresource/test/cases/log_subscriber_test.rb index b9143f5b67..ab5c22a783 100644 --- a/activeresource/test/cases/log_subscriber_test.rb +++ b/activeresource/test/cases/log_subscriber_test.rb @@ -23,7 +23,7 @@ class LogSubscriberTest < ActiveSupport::TestCase end def test_request_notification - matz = Person.find(1) + Person.find(1) wait assert_equal 2, @logger.logged(:info).size assert_equal "GET http://37s.sunrise.i:3000/people/1.json", @logger.logged(:info)[0] diff --git a/activeresource/test/cases/observing_test.rb b/activeresource/test/cases/observing_test.rb index ca3ab5d03d..58d3d389ff 100644 --- a/activeresource/test/cases/observing_test.rb +++ b/activeresource/test/cases/observing_test.rb @@ -37,7 +37,7 @@ class ObservingTest < Test::Unit::TestCase end def test_create_fires_save_and_create_notifications - rick = Person.create(:name => 'Rick') + Person.create(:name => 'Rick') assert_equal [:before_save, :before_create, :after_create, :after_save], self.history end diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 6b7044aeae..034d03533f 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,6 +1,14 @@ *Rails 3.2.0 (unreleased)* -* Removed ActiveSupport::SecureRandom in favour of SecureRandom from the standard library [Jon Leighton] +* 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 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/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb index e303eaa5ce..cc94041a1d 100644 --- a/activesupport/lib/active_support/benchmarkable.rb +++ b/activesupport/lib/active_support/benchmarkable.rb @@ -3,28 +3,28 @@ require 'active_support/core_ext/hash/keys' module ActiveSupport module Benchmarkable - # Allows you to measure the execution time of a block - # in a template and records the result to the log. Wrap this block around - # expensive operations or possible bottlenecks to get a time reading - # for the operation. For example, let's say you thought your file - # processing method was taking too long; you could wrap it in a benchmark block. + # Allows you to measure the execution time of a block in a template and records the result to + # the log. Wrap this block around expensive operations or possible bottlenecks to get a time + # reading for the operation. For example, let's say you thought your file processing method + # was taking too long; you could wrap it in a benchmark block. # # <% benchmark "Process data files" do %> # <%= expensive_files_operation %> # <% end %> # - # That would add something like "Process data files (345.2ms)" to the log, - # which you can then use to compare timings when optimizing your code. + # That would add something like "Process data files (345.2ms)" to the log, which you can then + # use to compare timings when optimizing your code. # - # You may give an optional logger level as the :level option. - # (:debug, :info, :warn, :error); the default value is :info. + # You may give an optional logger level (:debug, :info, :warn, :error) as the :level option. + # The default logger level value is :info. # # <% benchmark "Low-level files", :level => :debug do %> # <%= lowlevel_files_operation %> # <% end %> # - # Finally, you can pass true as the third argument to silence all log activity - # inside the block. This is great for boiling down a noisy block to just a single statement: + # Finally, you can pass true as the third argument to silence all log activity (other than the + # timing information) from inside the block. This is great for boiling down a noisy block to + # just a single statement that produces one log line: # # <% benchmark "Process data files", :level => :info, :silence => true do %> # <%= expensive_and_chatty_files_operation %> diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 10c457bb1d..ac88c82709 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -116,31 +116,32 @@ module ActiveSupport # cache.read("city") # => "Duckburgh" # # Keys are always translated into Strings and are case sensitive. When an - # object is specified as a key, its +cache_key+ method will be called if it - # is defined. Otherwise, the +to_param+ method will be called. Hashes and - # Arrays can be used as keys. The elements will be delimited by slashes - # and Hashes elements will be sorted by key so they are consistent. + # object is specified as a key and has a +cache_key+ method defined, this + # method will be called to define the key. Otherwise, the +to_param+ + # method will be called. Hashes and Arrays can also be used as keys. The + # elements will be delimited by slashes, and the elements within a Hash + # will be sorted by key so they are consistent. # # cache.read("city") == cache.read(:city) # => true # # Nil values can be cached. # - # If your cache is on a shared infrastructure, you can define a namespace for - # your cache entries. If a namespace is defined, it will be prefixed on to every - # key. The namespace can be either a static value or a Proc. If it is a Proc, it - # will be invoked when each key is evaluated so that you can use application logic - # to invalidate keys. + # If your cache is on a shared infrastructure, you can define a namespace + # for your cache entries. If a namespace is defined, it will be prefixed on + # to every key. The namespace can be either a static value or a Proc. If it + # is a Proc, it will be invoked when each key is evaluated so that you can + # use application logic to invalidate keys. # # cache.namespace = lambda { @last_mod_time } # Set the namespace to a variable # @last_mod_time = Time.now # Invalidate the entire cache by changing namespace # # - # Caches can also store values in a compressed format to save space and reduce - # time spent sending data. Since there is some overhead, values must be large - # enough to warrant compression. To turn on compression either pass - # <tt>:compress => true</tt> in the initializer or to +fetch+ or +write+. - # To specify the threshold at which to compress values, set - # <tt>:compress_threshold</tt>. The default threshold is 32K. + # Caches can also store values in a compressed format to save space and + # reduce time spent sending data. Since there is overhead, values must be + # large enough to warrant compression. To turn on compression either pass + # <tt>:compress => true</tt> in the initializer or as an option to +fetch+ + # or +write+. To specify the threshold at which to compress values, set the + # <tt>:compress_threshold</tt> option. The default threshold is 32K. class Store cattr_accessor :logger, :instance_writer => true @@ -180,11 +181,11 @@ module ActiveSupport # Fetches data from the cache, using the given key. If there is data in # the cache with the given key, then that data is returned. # - # If there is no such data in the cache (a cache miss occurred), - # then nil will be returned. However, if a block has been passed, then - # that block will be run in the event of a cache miss. The return value - # of the block will be written to the cache under the given cache key, - # and that return value will be returned. + # If there is no such data in the cache (a cache miss), then nil will be + # returned. However, if a block has been passed, that block will be run + # in the event of a cache miss. The return value of the block will be + # written to the cache under the given cache key, and that return value + # will be returned. # # cache.write("today", "Monday") # cache.fetch("today") # => "Monday" @@ -205,10 +206,11 @@ module ActiveSupport # in a compressed format. # # - # Setting <tt>:expires_in</tt> will set an expiration time on the cache. All caches - # support auto expiring content after a specified number of seconds. This value can - # be specified as an option to the construction in which call all entries will be - # affected. Or it can be supplied to the +fetch+ or +write+ method for just one entry. + # Setting <tt>:expires_in</tt> will set an expiration time on the cache. + # All caches support auto-expiring content after a specified number of + # seconds. This value can be specified as an option to the constructor + # (in which case all entries will be affected), or it can be supplied to + # the +fetch+ or +write+ method to effect just one entry. # # cache = ActiveSupport::Cache::MemoryStore.new(:expires_in => 5.minutes) # cache.write(key, value, :expires_in => 1.minute) # Set a lower value for one entry @@ -555,7 +557,7 @@ module ActiveSupport @expires_in = options[:expires_in] @expires_in = @expires_in.to_f if @expires_in @created_at = Time.now.to_f - if value + if defined?(value) if should_compress?(value, options) @value = Zlib::Deflate.deflate(Marshal.dump(value)) @compressed = true @@ -574,7 +576,7 @@ module ActiveSupport # Get the value stored in the cache. def value - if @value + if defined?(@value) val = compressed? ? Marshal.load(Zlib::Inflate.inflate(@value)) : @value unless val.frozen? val.freeze rescue nil diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 64d2c3bff6..e07294178b 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -183,6 +183,14 @@ module ActiveSupport # Provide support for raw values in the local cache strategy. module LocalCacheWithRaw # :nodoc: protected + def read_entry(key, options) + entry = super + if options[:raw] && local_cache && entry + entry = deserialize_entry(entry.value) + end + entry + end + def write_entry(key, entry, options) # :nodoc: retval = super if options[:raw] && local_cache && retval 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 7fabae3138..4834eca8b1 100644 --- a/activesupport/lib/active_support/core_ext/array/wrap.rb +++ b/activesupport/lib/active_support/core_ext/array/wrap.rb @@ -14,7 +14,7 @@ class Array # This method is similar in purpose to <tt>Kernel#Array</tt>, but there are some differences: # # * If the argument responds to +to_ary+ the method is invoked. <tt>Kernel#Array</tt> - # moves on to try +to_a+ if the returned value is +nil+, but <tt>Arraw.wrap</tt> returns + # moves on to try +to_a+ if the returned value is +nil+, but <tt>Array.wrap</tt> returns # such a +nil+ right away. # * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, <tt>Kernel#Array</tt> # raises an exception, while <tt>Array.wrap</tt> does not, it just returns the value. @@ -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/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb index a903735acf..268303aaf2 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb @@ -17,6 +17,7 @@ require 'active_support/core_ext/array/extract_options' # # To opt out of the instance writer method, pass :instance_writer => false. # To opt out of the instance reader method, pass :instance_reader => false. +# To opt out of both instance methods, pass :instance_accessor => false. # # class Person # cattr_accessor :hair_colors, :instance_writer => false, :instance_reader => false @@ -38,7 +39,7 @@ class Class end EOS - unless options[:instance_reader] == false + unless options[:instance_reader] == false || options[:instance_accessor] == false class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym} @@#{sym} @@ -61,7 +62,7 @@ class Class end EOS - unless options[:instance_writer] == false + unless options[:instance_writer] == false || options[:instance_accessor] == false class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym}=(obj) @@#{sym} = obj diff --git a/activesupport/lib/active_support/core_ext/date/freeze.rb b/activesupport/lib/active_support/core_ext/date/freeze.rb index 4edd715ba2..a731f8345e 100644 --- a/activesupport/lib/active_support/core_ext/date/freeze.rb +++ b/activesupport/lib/active_support/core_ext/date/freeze.rb @@ -5,7 +5,7 @@ # first call will result in a frozen object error since the memo # instance variable is uninitialized. # -# Work around by eagerly memoizing before freezing. +# Work around by eagerly memoizing before the first freeze. # # Ruby 1.9 uses a preinitialized instance variable so it's unaffected. # This hack is as close as we can get to feature detection: @@ -17,9 +17,11 @@ if RUBY_VERSION < '1.9' if frozen_object_error.message =~ /frozen/ class Date #:nodoc: def freeze - self.class.private_instance_methods(false).each do |m| - if m.to_s =~ /\A__\d+__\Z/ - instance_variable_set(:"@#{m}", [send(m)]) + unless frozen? + self.class.private_instance_methods(false).each do |m| + if m.to_s =~ /\A__\d+__\Z/ + instance_variable_set(:"@#{m}", [send(m)]) + end end end diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index 6d7f771b5d..3e05c6eaf2 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -96,7 +96,7 @@ module Enumerable # Returns true if the collection has more than 1 element. Functionally equivalent to collection.size > 1. # Can be called with a block too, much like any?, so people.many? { |p| p.age > 26 } returns true if more than 1 person is over 26. def many?(&block) - size = block_given? ? select(&block).size : self.size + size = block_given? ? count(&block) : self.size size > 1 end diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 102378a029..5f07bb4f5a 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -95,7 +95,7 @@ class Hash case value.class.to_s when 'Hash' if value['type'] == 'array' - _, entries = Array.wrap(value.detect { |k,v| k != 'type' }) + _, entries = Array.wrap(value.detect { |k,v| not v.is_a?(String) }) if entries.nil? || (c = value['__content__'] && c.blank?) [] else 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/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb index c6920098a8..526b8378a5 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -59,7 +59,7 @@ module Kernel raise unless exception_classes.any? { |cls| e.kind_of?(cls) } end end - + # Captures the given stream and returns it: # # stream = capture(:stdout) { puts "Cool" } diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb index 871f5cef3b..be94ae1565 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -12,7 +12,7 @@ class Module end EOS - unless options[:instance_reader] == false + unless options[:instance_reader] == false || options[:instance_accessor] == false class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym} @@#{sym} @@ -31,7 +31,7 @@ class Module end EOS - unless options[:instance_writer] == false + unless options[:instance_writer] == false || options[:instance_accessor] == false class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym}=(obj) @@#{sym} = obj @@ -53,6 +53,10 @@ class Module # end # # AppConfiguration.google_api_key = "overriding the api key!" + # + # To opt out of the instance writer method, pass :instance_writer => false. + # To opt out of the instance reader method, pass :instance_reader => false. + # To opt out of both instance methods, pass :instance_accessor => false. def mattr_accessor(*syms) mattr_reader(*syms) mattr_writer(*syms) diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 3a7652f5bf..1777a4b32d 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -113,7 +113,7 @@ class Module raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method." end - prefix = options[:prefix] && "#{options[:prefix] == true ? to : options[:prefix]}_" || '' + prefix = options[:prefix] ? "#{options[:prefix] == true ? to : options[:prefix]}_" : '' file, line = caller.first.split(':', 2) line = line.to_i diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index 8221dc4abe..d060ac04d8 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -1,3 +1,5 @@ +# encoding: utf-8 + class Object # An object is blank if it's false, empty, or a whitespace string. # For example, "", " ", +nil+, [], and {} are all blank. @@ -86,14 +88,18 @@ class Hash end class String + # 0x3000: fullwidth whitespace + NON_WHITESPACE_REGEXP = %r![^\s#{[0x3000].pack("U")}]! + # A string is blank if it's empty or contains whitespaces only: # # "".blank? # => true # " ".blank? # => true + # " ".blank? # => true # " something here ".blank? # => false # def blank? - self !~ /\S/ + self !~ NON_WHITESPACE_REGEXP end end diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb index 5b2cb6e331..664537eea4 100644 --- a/activesupport/lib/active_support/core_ext/string/conversions.rb +++ b/activesupport/lib/active_support/core_ext/string/conversions.rb @@ -34,9 +34,9 @@ class String # Form can be either :utc (default) or :local. def to_time(form = :utc) return nil if self.blank? - d = ::Date._parse(self, false).values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction).map { |arg| arg || 0 } + d = ::Date._parse(self, false).values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset).map { |arg| arg || 0 } d[6] *= 1000000 - ::Time.send("#{form}_time", *d) + ::Time.send("#{form}_time", *d[0..6]) - d[7] end def to_date 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 c27cbc37c5..3bf4edbdef 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -51,7 +51,8 @@ class ERB # <%=j @person.to_json %> # def json_escape(s) - s.to_s.gsub(/[&"><]/) { |special| JSON_ESCAPE[special] } + result = s.to_s.gsub(/[&"><]/) { |special| JSON_ESCAPE[special] } + s.html_safe? ? result.html_safe : result end alias j json_escape @@ -66,7 +67,7 @@ class Object end end -class Fixnum +class Numeric def html_safe? true end @@ -74,10 +75,34 @@ end module ActiveSupport #:nodoc: class SafeBuffer < String - alias safe_concat concat + 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_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)) @@ -90,15 +115,15 @@ module ActiveSupport #:nodoc: end def html_safe? - true + !dirty? end - def html_safe + def to_s self end - def to_s - self + def to_param + to_str end def encode_with(coder) @@ -107,17 +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, &block) # def gsub(*args, &block) + to_str.#{unsafe_method}(*args, &block) # to_str.gsub(*args, &block) + end # end + + def #{unsafe_method}!(*args) # def gsub!(*args) + @dirty = true # @dirty = true + super # super + end # 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/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 00fda2b370..c5fbbcf890 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -226,7 +226,7 @@ class Time end alias :at_end_of_quarter :end_of_quarter - # Returns a new Time representing the start of the year (1st of january, 0:00) + # Returns a new Time representing the start of the year (1st of january, 0:00) def beginning_of_year change(:month => 1, :day => 1, :hour => 0) end @@ -248,6 +248,31 @@ class Time advance(:days => 1) end + # Returns a Range representing the whole day of the current time. + def all_day + beginning_of_day..end_of_day + end + + # Returns a Range representing the whole week of the current time. + def all_week + beginning_of_week..end_of_week + end + + # Returns a Range representing the whole month of the current time. + def all_month + beginning_of_month..end_of_month + end + + # Returns a Range representing the whole quarter of the current time. + def all_quarter + beginning_of_quarter..end_of_quarter + end + + # Returns a Range representing the whole year of the current time. + def all_year + beginning_of_year..end_of_year + end + def plus_with_duration(other) #:nodoc: if ActiveSupport::Duration === other other.since(self) diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 26c5c157cb..d1543c4c58 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -628,17 +628,6 @@ module ActiveSupport #:nodoc: return [] end - class LoadingModule #:nodoc: - # Old style environment.rb referenced this method directly. Please note, it doesn't - # actually *do* anything any more. - def self.root(*args) - if defined?(Rails) && Rails.logger - Rails.logger.warn "Your environment.rb uses the old syntax, it may not continue to work in future releases." - Rails.logger.warn "For upgrade instructions please see: http://manuals.rubyonrails.com/read/book/19" - end - end - end - # Convert the provided const desc to a qualified constant name (as a string). # A module, class, symbol, or string may be provided. def to_constant_name(desc) #:nodoc: diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 15a3717ea1..59ffd24698 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -6,6 +6,8 @@ require 'active_support/core_ext/hash/keys' module ActiveSupport class HashWithIndifferentAccess < Hash + + # Always returns true, so that <tt>Array#extract_options!</tt> finds members of this class. def extractable_options? true 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/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb index 05da50e150..3864b1f5a6 100644 --- a/activesupport/lib/active_support/testing/assertions.rb +++ b/activesupport/lib/active_support/testing/assertions.rb @@ -68,7 +68,7 @@ module ActiveSupport # # A error message can be specified. # - # assert_no_difference 'Article.count', "An Article should not be destroyed" do + # assert_no_difference 'Article.count', "An Article should not be created" do # post :create, :article => invalid_attributes # end def assert_no_difference(expression, message = nil, &block) diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb index 02c19448fd..dd23f8d82d 100644 --- a/activesupport/lib/active_support/testing/performance.rb +++ b/activesupport/lib/active_support/testing/performance.rb @@ -9,7 +9,7 @@ module ActiveSupport module Testing module Performance extend ActiveSupport::Concern - + included do superclass_delegating_accessor :profile_options self.profile_options = {} @@ -20,7 +20,7 @@ module ActiveSupport include ForClassicTestUnit end end - + # each implementation should define metrics and freeze the defaults DEFAULTS = if ARGV.include?('--benchmark') # HAX for rake test @@ -32,7 +32,7 @@ module ActiveSupport :output => 'tmp/performance', :benchmark => false } end - + def full_profile_options DEFAULTS.merge(profile_options) end @@ -40,7 +40,7 @@ module ActiveSupport def full_test_name "#{self.class.name}##{method_name}" end - + module ForMiniTest def run(runner) @runner = runner @@ -53,7 +53,7 @@ module ActiveSupport end end end - + return end @@ -122,7 +122,7 @@ module ActiveSupport protected # overridden by each implementation def run_gc; end - + def run_warmup run_gc @@ -132,7 +132,7 @@ module ActiveSupport run_gc end - + def run_profile(metric) klass = full_profile_options[:benchmark] ? Benchmarker : Profiler performer = klass.new(self, metric) @@ -163,7 +163,7 @@ module ActiveSupport "#{full_profile_options[:output]}/#{full_test_name}_#{@metric.name}" end end - + # overridden by each implementation class Profiler < Performer def time_with_block @@ -171,7 +171,7 @@ module ActiveSupport yield Time.now - before end - + def run; end def record; end end @@ -181,10 +181,10 @@ module ActiveSupport super @supported = @metric.respond_to?('measure') end - + def run return unless @supported - + full_profile_options[:runs].to_i.times { run_test(@metric, :benchmark) } @total = @metric.total end @@ -237,7 +237,7 @@ module ActiveSupport "#{super}.csv" end end - + module Metrics def self.[](name) const_get(name.to_s.camelize) @@ -247,7 +247,7 @@ module ActiveSupport class Base include ActionView::Helpers::NumberHelper - + attr_reader :total def initialize @@ -265,15 +265,15 @@ module ActiveSupport @total += (measure - before) end end - + # overridden by each implementation def profile; end - + protected # overridden by each implementation def with_gc_stats; end end - + class Time < Base def measure ::Time.now.to_f @@ -287,19 +287,19 @@ module ActiveSupport end end end - + class Amount < Base def format(measurement) number_with_delimiter(measurement.floor) end end - + class DigitalInformationUnit < Base def format(measurement) number_to_human_size(measurement, :precision => 2) end end - + # each implementation provides its own metrics like ProcessTime, Memory or GcRuns end end diff --git a/activesupport/lib/active_support/testing/performance/jruby.rb b/activesupport/lib/active_support/testing/performance/jruby.rb index 326904bbac..b347539f13 100644 --- a/activesupport/lib/active_support/testing/performance/jruby.rb +++ b/activesupport/lib/active_support/testing/performance/jruby.rb @@ -1,5 +1,5 @@ require 'jruby/profiler' -require 'java' +require 'java' java_import java.lang.management.ManagementFactory module ActiveSupport @@ -12,21 +12,21 @@ module ActiveSupport { :metrics => [:wall_time], :formats => [:flat, :graph] } end).freeze - + protected def run_gc ManagementFactory.memory_mx_bean.gc - end + end class Profiler < Performer def initialize(*args) super @supported = @metric.is_a?(Metrics::WallTime) end - + def run return unless @supported - + @total = time_with_block do @data = JRuby::Profiler.profile do full_profile_options[:runs].to_i.times { run_test(@metric, :profile) } @@ -36,7 +36,7 @@ module ActiveSupport def record return unless @supported - + klasses = full_profile_options[:formats].map { |f| JRuby::Profiler.const_get("#{f.to_s.camelize}ProfilePrinter") }.compact klasses.each do |klass| @@ -61,7 +61,7 @@ module ActiveSupport end end - module Metrics + module Metrics class Base def profile yield @@ -85,7 +85,7 @@ module ActiveSupport ManagementFactory.thread_mx_bean.get_current_thread_cpu_time / 1000 / 1000 / 1000.0 # seconds end end - + class UserTime < Time def measure ManagementFactory.thread_mx_bean.get_current_thread_user_time / 1000 / 1000 / 1000.0 # seconds @@ -97,7 +97,7 @@ module ActiveSupport ManagementFactory.memory_mx_bean.non_heap_memory_usage.used + ManagementFactory.memory_mx_bean.heap_memory_usage.used end end - + class GcRuns < Amount def measure ManagementFactory.garbage_collector_mx_beans.inject(0) { |total_runs, current_gc| total_runs += current_gc.collection_count } diff --git a/activesupport/lib/active_support/testing/performance/rubinius.rb b/activesupport/lib/active_support/testing/performance/rubinius.rb index 198d235548..d9ebfbe352 100644 --- a/activesupport/lib/active_support/testing/performance/rubinius.rb +++ b/activesupport/lib/active_support/testing/performance/rubinius.rb @@ -10,12 +10,12 @@ module ActiveSupport { :metrics => [:wall_time], :formats => [:flat, :graph] } end).freeze - + protected def run_gc GC.run(true) end - + class Performer; end class Profiler < Performer @@ -23,35 +23,35 @@ module ActiveSupport super @supported = @metric.is_a?(Metrics::WallTime) end - + def run return unless @supported - + @profiler = Rubinius::Profiler::Instrumenter.new - + @total = time_with_block do @profiler.profile(false) do full_profile_options[:runs].to_i.times { run_test(@metric, :profile) } end end end - + def record return unless @supported - + if(full_profile_options[:formats].include?(:flat)) create_path_and_open_file(:flat) do |file| @profiler.show(file) end end - + if(full_profile_options[:formats].include?(:graph)) create_path_and_open_file(:graph) do |file| @profiler.show(file) end end end - + protected def create_path_and_open_file(printer_name) fname = "#{output_filename}_#{printer_name}.txt" @@ -62,10 +62,10 @@ module ActiveSupport end end - module Metrics + module Metrics class Base attr_reader :loopback - + def profile yield end diff --git a/activesupport/lib/active_support/testing/performance/ruby.rb b/activesupport/lib/active_support/testing/performance/ruby.rb index b29ec6719c..7d6d047ef6 100644 --- a/activesupport/lib/active_support/testing/performance/ruby.rb +++ b/activesupport/lib/active_support/testing/performance/ruby.rb @@ -16,7 +16,7 @@ module ActiveSupport :metrics => [:process_time, :memory, :objects], :formats => [:flat, :graph_html, :call_tree, :call_stack] } end).freeze - + protected def run_gc GC.start @@ -77,7 +77,7 @@ module ActiveSupport def measure_mode self.class::Mode end - + def profile RubyProf.resume yield @@ -91,7 +91,7 @@ module ActiveSupport yield end end - + class ProcessTime < Time Mode = RubyProf::PROCESS_TIME if RubyProf.const_defined?(:PROCESS_TIME) diff --git a/activesupport/lib/active_support/testing/performance/ruby/mri.rb b/activesupport/lib/active_support/testing/performance/ruby/mri.rb index 86e650050b..142279dd6e 100644 --- a/activesupport/lib/active_support/testing/performance/ruby/mri.rb +++ b/activesupport/lib/active_support/testing/performance/ruby/mri.rb @@ -15,7 +15,7 @@ module ActiveSupport end end end - + class Memory < DigitalInformationUnit # Ruby 1.8 + ruby-prof wrapper if RubyProf.respond_to?(:measure_memory) @@ -24,7 +24,7 @@ module ActiveSupport end end end - + class Objects < Amount # Ruby 1.8 + ruby-prof wrapper if RubyProf.respond_to?(:measure_allocations) @@ -33,7 +33,7 @@ module ActiveSupport end end end - + class GcRuns < Amount # Ruby 1.8 + ruby-prof wrapper if RubyProf.respond_to?(:measure_gc_runs) @@ -42,7 +42,7 @@ module ActiveSupport end end end - + class GcTime < Time # Ruby 1.8 + ruby-prof wrapper if RubyProf.respond_to?(:measure_gc_time) @@ -55,5 +55,3 @@ module ActiveSupport end end end - - diff --git a/activesupport/lib/active_support/testing/performance/ruby/yarv.rb b/activesupport/lib/active_support/testing/performance/ruby/yarv.rb index 62095a8fe4..7873262331 100644 --- a/activesupport/lib/active_support/testing/performance/ruby/yarv.rb +++ b/activesupport/lib/active_support/testing/performance/ruby/yarv.rb @@ -15,7 +15,7 @@ module ActiveSupport end end end - + class Memory < DigitalInformationUnit # Ruby 1.9 + GCdata patch if GC.respond_to?(:malloc_allocated_size) @@ -24,7 +24,7 @@ module ActiveSupport end end end - + class Objects < Amount # Ruby 1.9 + GCdata patch if GC.respond_to?(:malloc_allocations) @@ -33,7 +33,7 @@ module ActiveSupport end end end - + class GcRuns < Amount # Ruby 1.9 if GC.respond_to?(:count) @@ -42,7 +42,7 @@ module ActiveSupport end end end - + class GcTime < Time # Ruby 1.9 with GC::Profiler if defined?(GC::Profiler) && GC::Profiler.respond_to?(:total_time) diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 3d092529d6..ec2c717942 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -109,7 +109,7 @@ module ActiveSupport def xmlschema(fraction_digits = 0) fraction = if fraction_digits > 0 - ".%i" % time.usec.to_s[0, fraction_digits] + (".%06i" % time.usec)[0, fraction_digits + 1] end "#{time.strftime("%Y-%m-%dT%H:%M:%S")}#{fraction}#{formatted_offset(true, 'Z')}" diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb index e135872bf6..bd8e7f907a 100644 --- a/activesupport/lib/active_support/version.rb +++ b/activesupport/lib/active_support/version.rb @@ -1,9 +1,9 @@ module ActiveSupport module VERSION #:nodoc: MAJOR = 3 - MINOR = 1 + MINOR = 2 TINY = 0 - PRE = "rc1" + PRE = "beta" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index ed5ccb44de..8b3e4800c3 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -188,6 +188,11 @@ module CacheStoreBehavior assert_equal nil, @cache.read('foo') end + def test_should_read_and_write_false + assert_equal true, @cache.write('foo', false) + assert_equal false, @cache.read('foo') + end + def test_read_multi @cache.write('foo', 'bar') @cache.write('fu', 'baz') @@ -630,7 +635,14 @@ uses_memcached 'memcached backed store' do cache.write("foo", 2) assert_equal "2", cache.read("foo") end - + + def test_raw_values_with_marshal + cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true) + cache.clear + cache.write("foo", Marshal.dump([])) + assert_equal [], cache.read("foo") + end + def test_local_cache_raw_values cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true) cache.clear @@ -639,6 +651,15 @@ uses_memcached 'memcached backed store' do assert_equal "2", cache.read("foo") end end + + def test_local_cache_raw_values_with_marshal + cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true) + cache.clear + cache.with_local_cache do + cache.write("foo", Marshal.dump([])) + assert_equal [], cache.read("foo") + end + end end end 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/blank_test.rb b/activesupport/test/core_ext/blank_test.rb index 97c6b213ba..a2cf298905 100644 --- a/activesupport/test/core_ext/blank_test.rb +++ b/activesupport/test/core_ext/blank_test.rb @@ -1,8 +1,10 @@ +# encoding: utf-8 + require 'abstract_unit' require 'active_support/core_ext/object/blank' class BlankTest < Test::Unit::TestCase - BLANK = [ EmptyTrue.new, nil, false, '', ' ', " \n\t \r ", [], {} ] + BLANK = [ EmptyTrue.new, nil, false, '', ' ', " \n\t \r ", ' ', [], {} ] NOT = [ EmptyFalse.new, Object.new, true, 0, 1, 'a', [nil], { nil => 0 } ] def test_blank diff --git a/activesupport/test/core_ext/class/attribute_accessor_test.rb b/activesupport/test/core_ext/class/attribute_accessor_test.rb index 456f4b7948..6b50f8db37 100644 --- a/activesupport/test/core_ext/class/attribute_accessor_test.rb +++ b/activesupport/test/core_ext/class/attribute_accessor_test.rb @@ -5,8 +5,9 @@ class ClassAttributeAccessorTest < Test::Unit::TestCase def setup @class = Class.new do cattr_accessor :foo - cattr_accessor :bar, :instance_writer => false - cattr_reader :shaq, :instance_reader => false + cattr_accessor :bar, :instance_writer => false + cattr_reader :shaq, :instance_reader => false + cattr_accessor :camp, :instance_accessor => false end @object = @class.new end @@ -35,4 +36,10 @@ class ClassAttributeAccessorTest < Test::Unit::TestCase assert_respond_to @class, :shaq assert !@object.respond_to?(:shaq) end + + def test_should_not_create_instance_accessors + assert_respond_to @class, :camp + assert !@object.respond_to?(:camp) + assert !@object.respond_to?(:camp=) + end end 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/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index d81693209f..b4f848cd44 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -454,4 +454,10 @@ class DateExtBehaviorTest < Test::Unit::TestCase Date.today.freeze.inspect end end + + def test_can_freeze_twice + assert_nothing_raised do + Date.today.freeze.freeze + end + end end diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 0b3f18faec..1813ba2a4d 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -670,6 +670,55 @@ class HashToXmlTest < Test::Unit::TestCase assert_match %r{<local-created-at type=\"datetime\">1999-02-01T19:00:00-05:00</local-created-at>}, xml end + def test_multiple_records_from_xml_with_attributes_other_than_type_ignores_them_without_exploding + topics_xml = <<-EOT + <topics type="array" page="1" page-count="1000" per-page="2"> + <topic> + <title>The First Topic</title> + <author-name>David</author-name> + <id type="integer">1</id> + <approved type="boolean">false</approved> + <replies-count type="integer">0</replies-count> + <replies-close-in type="integer">2592000000</replies-close-in> + <written-on type="date">2003-07-16</written-on> + <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at> + <content>Have a nice day</content> + <author-email-address>david@loudthinking.com</author-email-address> + <parent-id nil="true"></parent-id> + </topic> + <topic> + <title>The Second Topic</title> + <author-name>Jason</author-name> + <id type="integer">1</id> + <approved type="boolean">false</approved> + <replies-count type="integer">0</replies-count> + <replies-close-in type="integer">2592000000</replies-close-in> + <written-on type="date">2003-07-16</written-on> + <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at> + <content>Have a nice day</content> + <author-email-address>david@loudthinking.com</author-email-address> + <parent-id></parent-id> + </topic> + </topics> + EOT + + expected_topic_hash = { + :title => "The First Topic", + :author_name => "David", + :id => 1, + :approved => false, + :replies_count => 0, + :replies_close_in => 2592000000, + :written_on => Date.new(2003, 7, 16), + :viewed_at => Time.utc(2003, 7, 16, 9, 28), + :content => "Have a nice day", + :author_email_address => "david@loudthinking.com", + :parent_id => nil + }.stringify_keys + + assert_equal expected_topic_hash, Hash.from_xml(topics_xml)["topics"].first + end + def test_single_record_from_xml topic_xml = <<-EOT <topic> diff --git a/activesupport/test/core_ext/module/attribute_accessor_test.rb b/activesupport/test/core_ext/module/attribute_accessor_test.rb index 118fb070a0..29889b51e0 100644 --- a/activesupport/test/core_ext/module/attribute_accessor_test.rb +++ b/activesupport/test/core_ext/module/attribute_accessor_test.rb @@ -7,6 +7,7 @@ class ModuleAttributeAccessorTest < Test::Unit::TestCase mattr_accessor :foo mattr_accessor :bar, :instance_writer => false mattr_reader :shaq, :instance_reader => false + mattr_accessor :camp, :instance_accessor => false end @class = Class.new @class.instance_eval { include m } @@ -37,4 +38,10 @@ class ModuleAttributeAccessorTest < Test::Unit::TestCase assert_respond_to @module, :shaq assert !@object.respond_to?(:shaq) end + + def test_should_not_create_instance_accessors + assert_respond_to @module, :camp + assert !@object.respond_to?(:camp) + assert !@object.respond_to?(:camp=) + end end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 32675c884a..4000cc913a 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -158,6 +158,7 @@ class StringInflectionsTest < Test::Unit::TestCase assert_equal Time.local(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time(:local) assert_equal DateTime.civil(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time assert_equal Time.local_time(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time(:local) + assert_equal Time.utc(2039, 2, 27, 23, 50), "2039-02-27 22:50 -0100".to_time assert_nil "".to_time end @@ -251,7 +252,7 @@ class StringInflectionsTest < Test::Unit::TestCase # And changes the original string: assert_equal original, expected end - + def test_string_inquiry assert "production".inquiry.production? assert !"production".inquiry.development? @@ -353,6 +354,10 @@ class OutputSafetyTest < ActiveSupport::TestCase test "A fixnum is safe by default" do assert 5.html_safe? end + + test "a float is safe by default" do + assert 5.7.html_safe? + end test "An object is unsafe by default" do assert !@object.html_safe? @@ -451,6 +456,12 @@ class OutputSafetyTest < ActiveSupport::TestCase assert !'ruby'.encoding_aware? end end + + test "call to_param returns a normal string" do + string = @string.html_safe + assert string.html_safe? + assert !string.to_param.html_safe? + end end class StringExcludeTest < ActiveSupport::TestCase diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 44e02109b1..c4c4381957 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -767,6 +767,26 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal false, Time === DateTime.civil(2000) end + def test_all_day + assert_equal Time.local(2011,6,7,0,0,0)..Time.local(2011,6,7,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_day + end + + def test_all_week + assert_equal Time.local(2011,6,6,0,0,0)..Time.local(2011,6,12,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_week + end + + def test_all_month + assert_equal Time.local(2011,6,1,0,0,0)..Time.local(2011,6,30,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_month + end + + def test_all_quarter + assert_equal Time.local(2011,4,1,0,0,0)..Time.local(2011,6,30,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_quarter + end + + def test_all_year + assert_equal Time.local(2011,1,1,0,0,0)..Time.local(2011,12,31,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_year + end + protected def with_env_tz(new_tz = 'US/Eastern') old_tz, ENV['TZ'] = ENV['TZ'], new_tz diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 72b55183ba..b2309ae806 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -111,6 +111,13 @@ class TimeWithZoneTest < Test::Unit::TestCase assert_equal "1999-12-31T19:00:00.123456-05:00", @twz.xmlschema(12) end + def test_xmlschema_with_fractional_seconds_lower_than_hundred_thousand + @twz += 0.001234 # advance the time by a fraction + assert_equal "1999-12-31T19:00:00.001-05:00", @twz.xmlschema(3) + assert_equal "1999-12-31T19:00:00.001234-05:00", @twz.xmlschema(6) + assert_equal "1999-12-31T19:00:00.001234-05:00", @twz.xmlschema(12) + end + def test_to_yaml assert_match(/^--- 2000-01-01 00:00:00(\.0+)?\s*Z\n/, @twz.to_yaml) end 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 a4e2acbb32..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,10 +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 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/test_test.rb b/activesupport/test/test_test.rb index ee5a20c789..5bd995aa32 100644 --- a/activesupport/test/test_test.rb +++ b/activesupport/test/test_test.rb @@ -50,7 +50,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase def test_expression_is_evaluated_in_the_appropriate_scope silence_warnings do - local_scope = 'foo' + local_scope = local_scope = 'foo' assert_difference('local_scope; @object.num') { @object.increment } 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/activesupport/test/whiny_nil_test.rb b/activesupport/test/whiny_nil_test.rb index ec3ca99ee6..1acaf7228f 100644 --- a/activesupport/test/whiny_nil_test.rb +++ b/activesupport/test/whiny_nil_test.rb @@ -33,11 +33,10 @@ class WhinyNilTest < Test::Unit::TestCase end def test_id - nil.stubs(:object_id).returns(999) nil.id rescue RuntimeError => nme assert_no_match(/nil:NilClass/, nme.message) - assert_match(/999/, nme.message) + assert_match(Regexp.new(nil.object_id.to_s), nme.message) end def test_no_to_ary_coercion diff --git a/ci/ci_build.rb b/ci/ci_build.rb index c3af1f0177..50f7f410fa 100755 --- a/ci/ci_build.rb +++ b/ci/ci_build.rb @@ -85,8 +85,8 @@ cd "#{root_dir}/activerecord" do puts "[CruiseControl] Building Active Record with MySQL IM enabled" puts ENV['IM'] = 'true' - build_results[:activerecord_mysql] = rake 'mysql:rebuild_databases', 'mysql:test' - build_results[:activerecord_mysql_isolated] = rake 'mysql:rebuild_databases', 'mysql:isolated_test' + build_results[:activerecord_mysql_IM] = rake 'mysql:rebuild_databases', 'mysql:test' + build_results[:activerecord_mysql_isolated_IM] = rake 'mysql:rebuild_databases', 'mysql:isolated_test' end cd "#{root_dir}/activerecord" do @@ -103,8 +103,8 @@ cd "#{root_dir}/activerecord" do puts "[CruiseControl] Building Active Record with MySQL2 IM enabled" puts ENV['IM'] = 'true' - build_results[:activerecord_mysql2] = rake 'mysql:rebuild_databases', 'mysql2:test' - build_results[:activerecord_mysql2_isolated] = rake 'mysql:rebuild_databases', 'mysql2:isolated_test' + build_results[:activerecord_mysql2_IM] = rake 'mysql:rebuild_databases', 'mysql2:test' + build_results[:activerecord_mysql2_isolated_IM] = rake 'mysql:rebuild_databases', 'mysql2:isolated_test' end cd "#{root_dir}/activerecord" do @@ -121,8 +121,8 @@ cd "#{root_dir}/activerecord" do puts "[CruiseControl] Building Active Record with PostgreSQL IM enabled" puts ENV['IM'] = 'true' - build_results[:activerecord_postgresql8] = rake 'postgresql:rebuild_databases', 'postgresql:test' - build_results[:activerecord_postgresql8_isolated] = rake 'postgresql:rebuild_databases', 'postgresql:isolated_test' + build_results[:activerecord_postgresql8_IM] = rake 'postgresql:rebuild_databases', 'postgresql:test' + build_results[:activerecord_postgresql8_isolated_IM] = rake 'postgresql:rebuild_databases', 'postgresql:isolated_test' end cd "#{root_dir}/activerecord" do @@ -139,8 +139,8 @@ cd "#{root_dir}/activerecord" do puts "[CruiseControl] Building Active Record with SQLite 3 IM enabled" puts ENV['IM'] = 'true' - build_results[:activerecord_sqlite3] = rake 'sqlite3:test' - build_results[:activerecord_sqlite3_isolated] = rake 'sqlite3:isolated_test' + build_results[:activerecord_sqlite3_IM] = rake 'sqlite3:test' + build_results[:activerecord_sqlite3_isolated_IM] = rake 'sqlite3:isolated_test' end cd "#{root_dir}/activerecord" do @@ -179,4 +179,3 @@ else puts "[CruiseControl] Failed components: #{failures.map { |component| component.first }.join(', ')}" exit(-1) end - diff --git a/ci/cruise_config.rb b/ci/cruise_config.rb index 9c7fa98a70..b64cd8aaea 100644 --- a/ci/cruise_config.rb +++ b/ci/cruise_config.rb @@ -1,9 +1,9 @@ Project.configure do |project| - # Send email notifications about broken and fixed builds to core mailing list - if Socket.gethostname =~ /ci.rubyonrails.org/ && ENV['ENABLE_RAILS_CI_EMAILS'] == 'true' - project.email_notifier.emails = ['rubyonrails-core@googlegroups.com'] - end - project.build_command = 'sudo gem update --system && ruby ci/ci_build.rb' - project.email_notifier.from = 'thewoolleyman@gmail.com' + project.email_notifier.from = 'rails-ci@wyeworks.com' + + # project.campfire_notifier.account = 'rails' + # project.campfire_notifier.token = '' + # project.campfire_notifier.room = 'Rails 3' + # project.campfire_notifier.ssl = true end diff --git a/ci/site_config.rb b/ci/site_config.rb index 09d5b550e8..f9db39ed57 100644 --- a/ci/site_config.rb +++ b/ci/site_config.rb @@ -45,16 +45,16 @@ Configuration.dashboard_refresh_interval = 60.seconds # Site-wide setting for the email "from" field. This can also be set on per-project basis, # through project.email.notifier.from attribute -Configuration.email_from = 'thewoolleyman+railsci@gmail.com' +Configuration.email_from = 'rails-ci@wyeworks.com' # Root URL of the dashboard application. Setting this attribute allows various notifiers to include a link to the # build page in the notification message. -Configuration.dashboard_url = 'http://ci.rubyonrails.org/' +Configuration.dashboard_url = 'http://rails-ci.wyeworks.com/' # If you don't want to allow triggering builds through dashboard Build Now button. Useful when you host CC.rb as a # public web site (such as http://cruisecontrolrb.thoughtworks.com/projects - try clicking on Build Now button there # and see what happens): -# Configuration.disable_build_now = true +Configuration.disable_build_now = true # If you want to only allow one project to build at a time, uncomment this line # by default, cruise allows multiple projects to build at a time diff --git a/load_paths.rb b/load_paths.rb index 15345e31e3..590af69330 100644 --- a/load_paths.rb +++ b/load_paths.rb @@ -1,9 +1,8 @@ # bust gem prelude if defined? Gem - Gem.source_index gem 'bundler' else require 'rubygems' end require 'bundler' -Bundler.setup
\ No newline at end of file +Bundler.setup diff --git a/railties/CHANGELOG b/railties/CHANGELOG index c77d03c39f..a9b4deb551 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,25 +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)* -* Application and plugin generation run bundle install unless --skip-gemfile or --skip-bundle. [fxn] +* Make sprockets/railtie require explicit and add --skip-sprockets to app generator [José Valim] -* Fixed database tasks for jdbc* adapters #jruby +* Added Rails.groups that automatically handles Rails.env and ENV["RAILS_GROUPS"] [José Valim] - [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_controller_overview.textile b/railties/guides/source/action_controller_overview.textile index 891bae3d5e..073e3bddcf 100644 --- a/railties/guides/source/action_controller_overview.textile +++ b/railties/guides/source/action_controller_overview.textile @@ -802,7 +802,7 @@ class DinnerController end </ruby> -Just like the filter, you could also passing +:only+ and +:except+ to enforce the secure connection only to specific actions +Just like the filter, you could also passing +:only+ and +:except+ to enforce the secure connection only to specific actions. <ruby> class DinnerController diff --git a/railties/guides/source/action_mailer_basics.textile b/railties/guides/source/action_mailer_basics.textile index e1ff49cd60..2eaee158ff 100644 --- a/railties/guides/source/action_mailer_basics.textile +++ b/railties/guides/source/action_mailer_basics.textile @@ -284,16 +284,37 @@ class UserMailer < ActionMailer::Base @user = user @url = "http://example.com/login" mail(:to => user.email, + :subject => "Welcome to My Awesome Site", + :template_path => 'notifications', + :template_name => 'another') + end + end + +end +</ruby> + +In this case it will look for templates at +app/views/notifications+ with name +another+. + +If you want more flexibility you can also pass a block and render specific templates or even render inline or text without using a template file: + +<ruby> +class UserMailer < ActionMailer::Base + default :from => "notifications@example.com" + + def welcome_email(user) + @user = user + @url = "http://example.com/login" + mail(:to => user.email, :subject => "Welcome to My Awesome Site") do |format| format.html { render 'another_template' } - format.text { render 'another_template' } + format.text { render :text => 'Render text' } end end end </ruby> -Will render 'another_template.text.erb' and 'another_template.html.erb'. The render command is the same one used inside of Action Controller, so you can use all the same options, such as <tt>:text</tt> etc. +This will render the template 'another_template.html.erb' for the HTML part and use the rendered text for the text part. The render command is the same one used inside of Action Controller, so you can use all the same options, such as <tt>:text</tt>, <tt>:inline</tt> etc. h4. Action Mailer Layouts diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile index b064851312..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: @@ -929,7 +929,7 @@ Creates a scope around a specific model object like form_for, but doesn‘t crea h5. file_field -Returns an file upload input tag tailored for accessing a specified attribute. +Returns a file upload input tag tailored for accessing a specified attribute. <ruby> file_field(:user, :avatar) @@ -1037,7 +1037,7 @@ Sample usage (selecting the associated Author for an instance of Post, +@post+): collection_select(:post, :author_id, Author.all, :id, :name_with_initial, {:prompt => true}) </ruby> -If @post.author_id is already 1, this would return: +If <tt>@post.author_id</tt> is 1, this would return: <html> <select name="post[author_id]"> @@ -1080,8 +1080,6 @@ Sample usage: option_groups_from_collection_for_select(@continents, :countries, :name, :id, :name, 3) </ruby> -TODO check above textile output looks right - Possible output: <html> @@ -1132,13 +1130,13 @@ h5. select Create a select tag and a series of contained option tags for the provided object and method. -Example with @post.person_id => 1: +Example: <ruby> select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { :include_blank => true }) </ruby> -could become: +If <tt>@post.person_id</tt> is 1, this would become: <html> <select name="post[person_id]"> @@ -1189,7 +1187,7 @@ h5. file_field_tag Creates a file upload field. -If you are using file uploads then you will also need to set the multipart option for the form tag: +Prior to Rails 3.1, if you are using file uploads, then you will need to set the multipart option for the form tag. Rails 3.1+ does this automatically. <ruby> <%= form_tag { :action => "post" }, { :multipart => true } do %> @@ -1400,102 +1398,6 @@ number_with_precision(111.2345) # => 111.235 number_with_precision(111.2345, 2) # => 111.23 </ruby> -h5. evaluate_remote_response - -Returns +eval(request.responseText)+ which is the JavaScript function that form_remote_tag can call in +:complete+ to evaluate a multiple update return document using +update_element_function+ calls. - -h5. form_remote_tag - -Returns a form tag that will submit using XMLHttpRequest in the background instead of the regular reloading POST arrangement. Even though it‘s using JavaScript to serialize the form elements, the form submission will work just like a regular submission as viewed by the receiving side. - -For example, this: - -<ruby> -form_remote_tag :html => { :action => url_for(:controller => "some", :action => "place") } -</ruby> - -would generate the following: - -<html> -<form action="/some/place" method="post" onsubmit="new Ajax.Request('', - {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;"> -</html> - -h5. link_to_remote - -Returns a link to a remote action that's called in the background using XMLHttpRequest. You can generate a link that uses AJAX in the general case, while degrading gracefully to plain link behavior in the absence of JavaScript. For example: - -<ruby> -link_to_remote "Delete this post", - { :update => "posts", :url => { :action => "destroy", :id => post.id } }, - :href => url_for(:action => "destroy", :id => post.id) -</ruby> - -h5. observe_field - -Observes the field specified and calls a callback when its contents have changed. - -<ruby> -observe_field("my_field", :function => "alert('Field changed')") -</ruby> - -h5. observe_form - -Observes the form specified and calls a callback when its contents have changed. The options for observe_form are the same as the options for observe_field. - -<ruby> -observe_field("my_form", :function => "alert('Form changed')") -</ruby> - -h5. periodically_call_remote - -Periodically calls the specified url as often as specified. Usually used to update a specified div with the results of the remote call. The following example will call update every 20 seconds and update the news_block div: - -<ruby> -periodically_call_remote(:url => 'update', :frequency => '20', :update => 'news_block') -# => PeriodicalExecuter(function() {new Ajax.Updater('news_block', 'update', {asynchronous:true, evalScripts:true})}, 20) -</ruby> - -h5. remote_form_for - -Creates a form that will submit using XMLHttpRequest in the background instead of the regular reloading POST arrangement and a scope around a specific resource that is used as a base for questioning about values for the fields. - -<ruby> -<%= remote_form_for(@post) do |f| %> - ... -<% end %> -</ruby> - -h5. remote_function - -Returns the JavaScript needed for a remote function. Takes the same arguments as +link_to_remote+. - -<ruby> -<select id="options" onchange="<%= remote_function(:update => "options", :url => { :action => :update_options }) %>"> - <option value="0">Hello</option> - <option value="1">World</option> -</select> -# => <select id="options" onchange="new Ajax.Updater('options', '/testing/update_options', {asynchronous:true, evalScripts:true})"> -</ruby> - -h5. submit_to_remote - -Returns a button input tag that will submit form using XMLHttpRequest in the background instead of a regular POST request that reloads the page. - -For example, the following: - -<ruby> -submit_to_remote 'create_btn', 'Create', :url => { :action => 'create' } -</ruby> - -would generate: - -<html> -<input name="create_btn" onclick="new Ajax.Request('/testing/create', - {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)}); - return false;" type="button" value="Create" /> -</html> - h3. Localized Views Action View has the ability render different templates depending on the current locale. @@ -1520,6 +1422,7 @@ You can read more about the Rails Internationalization (I18n) API "here":i18n.ht h3. Changelog +* May 29, 2011: Removed references to remote_* helpers - Vijay Dev * April 16, 2011: Added 'Using Action View with Rails', 'Templates' and 'Partials' sections. "Sebastian Martinez":http://wyeworks.com * September 3, 2009: Continuing work by Trevor Turk, leveraging the Action Pack docs and "What's new in Edge Rails":http://ryandaigle.com/articles/2007/8/3/what-s-new-in-edge-rails-partials-get-layouts * April 5, 2009: Starting work by Trevor Turk, leveraging Mike Gunderloy's docs diff --git a/railties/guides/source/active_record_basics.textile b/railties/guides/source/active_record_basics.textile index b7926f3a3b..3e46e7df9f 100644 --- a/railties/guides/source/active_record_basics.textile +++ b/railties/guides/source/active_record_basics.textile @@ -64,8 +64,8 @@ There are also some optional column names that will create additional features t * *created_on* - Automatically gets set to the current date when the record is first created. * *updated_at* - Automatically gets set to the current date and time whenever the record is updated. * *updated_on* - Automatically gets set to the current date whenever the record is updated. -* *lock_version* - Adds "optimistic locking":http://api.rubyonrails.com/classes/ActiveRecord/Locking.html to a model. -* *type* - Specifies that the model uses "Single Table Inheritance":http://api.rubyonrails.com/classes/ActiveRecord/Base.html +* *lock_version* - Adds "optimistic locking":http://api.rubyonrails.org/classes/ActiveRecord/Locking.html to a model. +* *type* - Specifies that the model uses "Single Table Inheritance":http://api.rubyonrails.org/classes/ActiveRecord/Base.html * *(table_name)_count* - Used to cache the number of belonging objects on associations. For example, a +comments_count+ column in a +Post+ class that has many instances of +Comment+ will cache the number of existent comments for each post. NOTE: While these column names are optional they are in fact reserved by Active Record. Steer clear of reserved keywords unless you want the extra functionality. For example, "type" is a reserved keyword used to designate a table using Single Table Inheritance. If you are not using STI, try an analogous keyword like "context", that may still accurately describe the data you are modeling. diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index 579a323d57..082f9eda7d 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+ @@ -255,7 +256,7 @@ The above will yield the supplied block with +1000+ invoices every time. h3. Conditions -The +find+ method allows you to specify conditions to limit the records returned, representing the +WHERE+-part of the SQL statement. Conditions can either be specified as a string, array, or hash. +The +where+ method allows you to specify conditions to limit the records returned, representing the +WHERE+-part of the SQL statement. Conditions can either be specified as a string, array, or hash. h4. Pure String Conditions @@ -465,7 +466,7 @@ To apply a +GROUP BY+ clause to the SQL fired by the finder, you can specify the For example, if you want to find a collection of the dates orders were created on: <ruby> -Order.group("date(created_at)").order("created_at") +Order.select("date(created_at) as ordered_date, sum(price) as total_price").group("date(created_at)") </ruby> And this will give you a single +Order+ object for each date where there are orders in the database. @@ -473,7 +474,7 @@ And this will give you a single +Order+ object for each date where there are ord The SQL that would be executed would be something like this: <sql> -SELECT * FROM orders GROUP BY date(created_at) ORDER BY created_at +SELECT date(created_at) as ordered_date, sum(price) as total_price FROM orders GROUP BY date(created_at) </sql> h3. Having @@ -483,16 +484,16 @@ SQL uses the +HAVING+ clause to specify conditions on the +GROUP BY+ fields. You For example: <ruby> -Order.group("date(created_at)").having("created_at > ?", 1.month.ago) +Order.select("date(created_at) as ordered_date, sum(price) as total_price").group("date(created_at)").having("sum(price) > ?", 100) </ruby> The SQL that would be executed would be something like this: <sql> -SELECT * FROM orders GROUP BY date(created_at) HAVING created_at > '2009-01-15' +SELECT date(created_at) as ordered_date, sum(price) as total_price FROM orders GROUP BY date(created_at) HAVING sum(price) > 100 </sql> -This will return single order objects for each day, but only for the last month. +This will return single order objects for each day, but only those that are ordered more than $100 in a day. h3. Overriding Conditions @@ -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. @@ -675,7 +702,7 @@ class Post < ActiveRecord::Base has_many :tags end -class Comments < ActiveRecord::Base +class Comment < ActiveRecord::Base belongs_to :post has_one :guest end @@ -683,6 +710,10 @@ end class Guest < ActiveRecord::Base belongs_to :comment end + +class Tag < ActiveRecord::Base + belongs_to :post +end </ruby> Now all of the following will produce the expected join queries using +INNER JOIN+: @@ -700,6 +731,8 @@ SELECT categories.* FROM categories INNER JOIN posts ON posts.category_id = categories.id </sql> +Or, in English: "return a Category object for all categories with posts". Note that you will see duplicate categories if more than one post has the same category. If you want unique categories, you can use Category.joins(:post).select("distinct(categories.id)"). + h5. Joining Multiple Associations <ruby> @@ -714,18 +747,40 @@ SELECT posts.* FROM posts INNER JOIN comments ON comments.post_id = posts.id </sql> +Or, in English: "return all posts that have a category and at least one comment". Note again that posts with multiple comments will show up multiple times. + h5. Joining Nested Associations (Single Level) <ruby> Post.joins(:comments => :guest) </ruby> +This produces: + +<sql> +SELECT posts.* FROM posts + INNER JOIN comments ON comments.post_id = posts.id + INNER JOIN guests ON guests.comment_id = comments.id +</sql> + +Or, in English: "return all posts that have a comment made by a guest." + h5. Joining Nested Associations (Multiple Level) <ruby> Category.joins(:posts => [{:comments => :guest}, :tags]) </ruby> +This produces: + +<sql> +SELECT categories.* FROM categories + INNER JOIN posts ON posts.category_id = categories.id + INNER JOIN comments ON comments.post_id = posts.id + INNER JOIN guests ON guests.comment_id = comments.id + INNER JOIN tags ON tags.post_id = posts.id +</sql> + h4. Specifying Conditions on the Joined Tables You can specify conditions on the joined tables using the regular "Array":#array-conditions and "String":#pure-string-conditions conditions. "Hash conditions":#hash-conditions provides a special syntax for specifying conditions for the joined tables: @@ -910,6 +965,47 @@ Using a class method is the preferred way to accept arguments for scopes. These category.posts.1_week_before(time) </ruby> +h4. Working with scopes + +Where a relational object is required, the +scoped+ method may come in handy. This will return an +ActiveRecord::Relation+ object which can have further scoping applied to it afterwards. A place where this may come in handy is on associations + +<ruby> +client = Client.find_by_first_name("Ryan") +orders = client.orders.scoped +</ruby> + +With this new +orders+ object, we are able to ascertain that this object can have more scopes applied to it. For instance, if we wanted to return orders only in the last 30 days at a later point. + +<ruby> +orders.where("created_at > ?", 30.days.ago) +</ruby> + +h4. Applying a default scope + +If we wish for a scope to be applied across all queries to the model we can use the +default_scope+ method within the model itself. + +<ruby> +class Client < ActiveRecord::Base + default_scope where("removed_at IS NULL") +end +</ruby> + +When queries are executed on this model, the SQL query will now look something like this: + +<sql> +SELECT * FROM clients WHERE removed_at IS NULL +</sql> + +h4. Removing all scoping + +If we wish to remove scoping for any reason we can use the +unscoped+ method. This is especially useful if a +default_scope+ is specified in the model and should not be applied for this particular query. + +<ruby> +Client.unscoped.all +</ruby> + +This method removes all scoping and will do a normal query on the table. + h3. Dynamic Finders For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called +first_name+ on your +Client+ model for example, you get +find_by_first_name+ and +find_all_by_first_name+ for free from Active Record. If you have a +locked+ field on the +Client+ model, you also get +find_by_locked+ and +find_all_by_locked+ methods. @@ -1091,7 +1187,8 @@ For options, please see the parent section, "Calculations":#calculations. h3. Changelog -* December 23 2010: Add documentation for the +scope+ method. "Ryan Bigg":http://ryanbigg.com +* June 26 2011: Added documentation for the +scoped+, +unscoped+ and +default+ methods. "Ryan Bigg":credits.html#radar +* December 23 2010: Add documentation for the +scope+ method. "Ryan Bigg":credits.html#radar * April 7, 2010: Fixed document to validate XHTML 1.0 Strict. "Jaime Iniesta":http://jaimeiniesta.com * February 3, 2010: Update to Rails 3 by "James Miller":credits.html#bensie * February 7, 2009: Second version by "Pratik":credits.html#lifo diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile index 9f59397d7d..50ff1c9ff7 100644 --- a/railties/guides/source/active_record_validations_callbacks.textile +++ b/railties/guides/source/active_record_validations_callbacks.textile @@ -82,6 +82,7 @@ The following methods skip validations, and will save the object to the database * +increment!+ * +increment_counter+ * +toggle!+ +* +touch+ * +update_all+ * +update_attribute+ * +update_column+ @@ -181,7 +182,7 @@ It can receive an +:accept+ option, which determines the value that will be cons <ruby> class Person < ActiveRecord::Base - validates :terms_of_service, :acceptance => true, :accept => 'yes' + validates :terms_of_service, :acceptance => { :accept => 'yes' } end </ruby> @@ -337,7 +338,7 @@ WARNING. Note that the regular expression above allows a trailing newline charac <ruby> class Player < ActiveRecord::Base validates :points, :numericality => true - validates :games_played, :numericality => true, :only_integer => true + validates :games_played, :numericality => { :only_integer => true } end </ruby> @@ -392,8 +393,8 @@ There is a +:scope+ option that you can use to specify other attributes that are <ruby> class Holiday < ActiveRecord::Base - validates :name, :uniqueness => true, :scope => :year, - :message => "should happen once per year" + validates :name, :uniqueness => { :scope => :year, + :message => "should happen once per year" } end </ruby> @@ -401,7 +402,7 @@ There is also a +:case_sensitive+ option that you can use to define whether the <ruby> class Person < ActiveRecord::Base - validates :name, :uniqueness => true, :case_sensitive => false + validates :name, :uniqueness => { :case_sensitive => false } end </ruby> @@ -429,7 +430,7 @@ end The +validates_with+ helper takes a class, or a list of classes to use for validation. There is no default error message for +validates_with+. You must manually add errors to the record's errors collection in the validator class. -To implement the validate method, you must have an +record+ parameter defined, which is the record to be validated. +To implement the validate method, you must have a +record+ parameter defined, which is the record to be validated. Like all other validations, +validates_with+ takes the +:if+, +:unless+ and +:on+ options. If you pass any other options, it will send those options to the validator class as +options+: @@ -911,20 +912,20 @@ h4. Creating an Object * +before_validation+ * +after_validation+ * +before_save+ -* +after_save+ * +before_create+ * +around_create+ * +after_create+ +* +after_save+ h4. Updating an Object * +before_validation+ * +after_validation+ * +before_save+ -* +after_save+ * +before_update+ * +around_update+ * +after_update+ +* +after_save+ h4. Destroying an Object @@ -1007,6 +1008,7 @@ Just as with validations, it's also possible to skip callbacks. These methods sh * +increment+ * +increment_counter+ * +toggle+ +* +touch+ * +update_column+ * +update_all+ * +update_counters+ diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 43aa6dfbbb..a0ed85cf01 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -868,7 +868,7 @@ The macro accepts several methods: delegate :name, :age, :address, :twitter, :to => :profile </ruby> -When interpolated into a string, the +:to+ option should become an expression that evaluates to the object the method is delegated to. Typically a string or symbol. Such a expression is evaluated in the context of the receiver: +When interpolated into a string, the +:to+ option should become an expression that evaluates to the object the method is delegated to. Typically a string or symbol. Such an expression is evaluated in the context of the receiver: <ruby> # delegates to the Rails constant @@ -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+. @@ -2205,8 +2249,8 @@ The method +Array.wrap+ wraps its argument in an array unless it is already an a Specifically: * If the argument is +nil+ an empty list is returned. -* Otherwise, if the argument responds to +to_ary+ it is invoked, and its result returned. -* Otherwise, returns an array with the argument as its single element. +* Otherwise, if the argument responds to +to_ary+ it is invoked, and if the value of +to_ary+ is not +nil+, it is returned. +* Otherwise, an array with the argument as its single element is returned. <ruby> Array.wrap(nil) # => [] @@ -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+. @@ -3024,7 +3068,7 @@ Date.new(2010, 1, 31).change(:month => 2) h5(#date-durations). Durations -Durations can be added and substracted to dates: +Durations can be added to and subtracted from dates: <ruby> d = Date.current @@ -3232,7 +3276,7 @@ DateTime.current.change(:month => 2, :day => 30) h5(#datetime-durations). Durations -Durations can be added and substracted to datetimes: +Durations can be added to and subtracted from datetimes: <ruby> now = DateTime.current @@ -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+: @@ -3353,7 +3423,7 @@ If the time to be constructed lies beyond the range supported by +Time+ in the r h5(#time-durations). Durations -Durations can be added and substracted to time objects: +Durations can be added to and subtracted from time objects: <ruby> now = Time.current diff --git a/railties/guides/source/ajax_on_rails.textile b/railties/guides/source/ajax_on_rails.textile index 1566c23414..8b72e20c33 100644 --- a/railties/guides/source/ajax_on_rails.textile +++ b/railties/guides/source/ajax_on_rails.textile @@ -72,7 +72,7 @@ link_to_remote "Add to cart", If the server returns 200, the output of the above example is equivalent to our first, simple one. However, in case of error, the element with the DOM id +error+ is updated rather than the +cart+ element. -** *position* By default (i.e. when not specifying this option, like in the examples before) the repsonse is injected into the element with the specified DOM id, replacing the original content of the element (if there was any). You might want to alter this behavior by keeping the original content - the only question is where to place the new content? This can specified by the +position+ parameter, with four possibilities: +** *position* By default (i.e. when not specifying this option, like in the examples before) the response is injected into the element with the specified DOM id, replacing the original content of the element (if there was any). You might want to alter this behavior by keeping the original content - the only question is where to place the new content? This can specified by the +position+ parameter, with four possibilities: *** +:before+ Inserts the response text just before the target element. More precisely, it creates a text node from the response and inserts it as the left sibling of the target element. *** +:after+ Similar behavior to +:before+, but in this case the response is inserted after the target element. *** +:top+ Inserts the text into the target element, before it's original content. If the target element was empty, this is equivalent with not specifying +:position+ at all. diff --git a/railties/guides/source/api_documentation_guidelines.textile b/railties/guides/source/api_documentation_guidelines.textile index e046bc5cbb..50e86e05a8 100644 --- a/railties/guides/source/api_documentation_guidelines.textile +++ b/railties/guides/source/api_documentation_guidelines.textile @@ -41,8 +41,8 @@ h3. Example Code Choose meaningful examples that depict and cover the basics as well as interesting points or gotchas. -Use two spaces to indent chunks of code.—that is two spaces with respect to the left margin; the examples -themselves should use "Rails code conventions":http://rails.lighthouseapp.com/projects/8994/source-style. +Use two spaces to indent chunks of code--that is two spaces with respect to the left margin; the examples +themselves should use "Rails coding conventions":contributing_to_ruby_on_rails.html#follow-the-coding-conventions. Short docs do not need an explicit "Examples" label to introduce snippets, they just follow paragraphs: diff --git a/railties/guides/source/asset_pipeline.textile b/railties/guides/source/asset_pipeline.textile index 9ea1aa9e01..563c1c79ae 100644 --- a/railties/guides/source/asset_pipeline.textile +++ b/railties/guides/source/asset_pipeline.textile @@ -3,27 +3,333 @@ 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 pipline -* Adding a preproccessor to the pipeline -* Package assets with your plugin +* Understand the benefits of the asset pipeline +* Adding a pre-processor to the pipeline +* Package assets with a gem endprologue. h3. What Is The Asset Pipeline? -h4. Why Should I Use it? +The asset pipeline provides a framework to concatenate and minify or compress Javascript and CSS assets. It also adds the ability to write these assets in other languages such as CoffeeScript, SCSS and ERB. + +Prior to Rails 3.1 these features were added through third-party Ruby libraries such as Jammit and Sprockets. Rails 3.1 includes the +sprockets-rails+ gem, which depends on the +sprockets+ gem, by default. + +By having this as a core feature of Rails, all developers can benefit from the power of having their assets pre-processed, compressed and minified by one central library, Sprockets. This is part of Rails' "Fast by default" strategy as outlined by DHH in his 2011 keynote at Railsconf. + +In new Rails 3.1 application the asset pipeline is enable by default. It can be disabled in +application.rb+ by putting this line inside the +Application+ class definition: + +<plain> + config.assets.enabled = false +</plain> + +It is recommended that you use the defaults for all new apps. + + +h4. Main Features + +The first is to concatenate of assets. This is important in a production environment to reduce the number of requests that a client browser has to make to render a web page. While Rails already has a feature to concatenate these types of asset--by placing +:cache => true+ at the end of tags such as +javascript_include_tag+ and +stylesheet_link_tag+--, many people do not use it. + +The default behavior in Rails 3.1 and onward is to concatenate all files into one master file each for JS and CSS, however you can separate files or groups of files if required (see below). In production an MD5 fingerprint is inserted into each filename. + +The second feature of the pipeline is to minify or compress. For CSS this usually involves removing whitespace and comments. For Javascript more complex processes can be applied. + +You can choose from a set of built in options or specify your own. + +The third feature is the ability to code these assets using another language, or language extension. These include SCSS or Sass for CSS, CoffeeScript for Javascript, and ERB for both. + +h4. What is fingerprinting and why should I care? + +Fingerprinting is a technique where the filenames of content that is static or infrequently updated is altered to be unique to the content contained in the file. + +When a filename is unique and based on its content, http headers can be set to encourage caches everywhere (at ISPs, in browsers) to keep there own copy of the content. When the content is updated, the fingerprint will change and the remote clients will request the new file. This is generally known as _cachebusting_. + +The most effective technique is to insert a hash of the content into the name, usually at the end. For example a CSS file +global.css+ is hashed and the filename is updated to incorporate the hash. + +<plain> +global.css => global-908e25f4bf641868d8683022a5b62f54.css +</plain> + +This is the strategy adopted by the Rails asset pipeline. + +Rails old strategy was to append a query string to every asset linked with a built-in helper. In the source the generated code looked like this: + +<plain> +/stylesheets/global.css?1309495796 +</plain> + +This has several disadvantages: + +1. Not all caches will cache content with a query string + +"Steve Souders recommends":http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/, "...avoiding a querystring for cacheable resources". He found that in these case 5-20% of requests will not be cached. + +2. The filename can change between nodes in multi-server environments. + +The query string in Rails is based on the files mtime (mtime is the file modification time). When assets are deployed to a cluster, there is no guarantee that the timestamps will be the same, resulting in different values being used depending on which server handles the request. + +The other problems is that when static assets are deployed with each new release of code, the mtime of *all* these files changes, forcing all remote clients to fetch them again, even when the content of those assets has not changed. + +Fingerprinting avoids all these problems be ensuring filenames are consistent based on the content. + +More reading: + +* "Optimize caching":http://code.google.com/speed/page-speed/docs/caching.html +* "Revving Filenames: don’t use querystring":http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/ + h3. How to Use the Asset Pipeline +In previous versions of Rails, all assets were located in subdirectories of +public+ 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 and will be served as static files by the application or web server. 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 Cascading 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, as these files can then be loaded just for these controllers with lines such as +<%= javascript_include_tag params[:controller] %>+ or +<%= stylesheet_link_tag params[:controller] %>+. + h4. Asset Organization -h4. Default Files Loaded +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. + +All subdirectories 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 served. + +h4. Coding links to Assets + +To access 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 the current environment's file is not set to +false+), this file will be served by Sprockets unless a file at +public/assets/rails.png+ exists, in which case that file will be served. Alternatively, a file with an MD5 hash after its name such as +public/assets/rails-af27b6a414e6da00003503148be9b409.png+ will also be picked up by Sprockets. How these hashes are generated is covered in the "Production Assets":#production_assets section later on in this guide. + +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+. + +<erb> + <%= stylesheet_link_tag "application" %> + <%= javascript_include_tag "application" %> +</erb> + +These helpers (when the pipeline is on) are providing links to the compiled manifest with the specified name (or names). + +h4. Manifest Files and Directives + +Sprockets uses manifest files to determine which assets to include and serve. These manifest files contain _directives_ - instructions that tell Sprockets 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 is 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 + +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. + +There's also a default +app/assets/stylesheets/application.css+ file which contains these lines: + +<plain> +/* ... +*= require_self +*= require_tree . +*/ +</plain> + +The directives that work in the JavaScript files will also work in stylesheets, obviously including 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. + +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. + +You can have as many manifest files as you need. For example the +admin.css+ and +admin.js+ manifest could contain the JS and CSS files that are used for the admin section of an application. + +For some assets (like CSS) the compiled order is important. You can specify individual files and they will be compiled in the order specified: + +<plain> +/* ... +*= require reset +*= require layout +*= require chrome +*/ +</plain> + + +h4. Preprocessing + +The file extensions used on an asset will determine what preprocssing will be applied. When a controller or a scaffold is generated with the default Rails gemset, 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. + +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. + +Additional layers of pre-processing can be requested by adding other extensions. These should be used in the order the processing should be applied. For example, a stylesheet called +app/assets/stylesheets/projects.css.scss.erb+ would first be processed as ERB, then SCSS and finally served as CSS. The same applies to a JavaScript file - +app/assets/javascripts/projects.js.coffee.erb+ would be process as ERB, CoffeeScript and served as JavaScript. + +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. + +h3. In Development + +TODO: Talk about: Rack::Cache's caching (used in dev and production. The only difference is hashing and headers). + +In the development environment assets are compiled and cached on the first request after the server is started. Sprockets sets a +must-validate+ cache-control http header to reduce request overhead on subsequent requests - on these the browser gets a 304 (not-modified) response. + +If any of the files in the manifest have changed between requests, the server will respond with a new compiled file. + +h4. Debugging Assets + +You can put +?debug_assets=true+ or +?debug_assets=1+ at the end of a URL and Sprockets will expand the lines which load the assets. For example, if we had an +app/assets/javascripts/application.js+ file containing these lines: + +<plain> +//= require "projects" +//= require "tickets" +</plain> + +By default, this would only render this line when used with +<%= javascript_include_tag "application" %>+ in a view or layout: + +<html> + <script src='/assets/application.js'></script> +</html> + +When the +debug_assets+ parameter is set, this line will be expanded out into three separate lines, separating out the combined file into their parts. + +<html> + <script src='/assets/application.js'></script> + <script src='/assets/projects.js'></script> + <script src='/assets/tickets.js'></script> +</html> + +This allows the individual parts of an asset to be rendered and debugged separately. + +h3. In Production + +In the production environment, assets are served slightly differently. + +On the first request the assets are compiled and cached as described above, however the manifest names are altered to include an MD5 hash. Files names typically will look like these: + +<plain> +/assets/application-908e25f4bf641868d8683022a5b62f54.js +/assets/application-4dd5b109ee3439da54f5bdfd78a80473.css +</plain> + +The MD5 is generated from the contents of the compiled files, and is included in the http +Content-MD5+ header. + +Sprockets also sets the +Cache-Control+ http header to +max-age=31536000+. This signals all caches between your server and the client browser that this content (the file served) can be cached for 1 year. The effect of this is to reduce the number of requests for this asset from your server; the asset has a good chance of being in the local browser cache or some intermediate cache. + +This behavior is controlled by the setting of +config.action_controller.perform_caching+ setting in Rails (which is +true+ for production, +false+ for everything else). This value is propagated to Sprockets during initialization for use when action_controller is not available. + +TODO: +describe each and the differences between: + * Sass-rails's handy +image_url+ helpers + * ERB pre-processing and +asset_path+ + +h4. Precompiling assets + +Even though assets are served by Rack::Cache with far-future headers, in high traffic sites this may not be fast enough. + +Rails comes bundled with a rake task to compile the manifests to files on disc. These are located in the +public/assets+ directory where they will be served by your web server instead of the Rails application. + +TODO: Add section about image assets + +The rake task is: + +<erb> +rake assets:precompile +</erb> + +TODO: explain where to use this with Capistrano + +TODO: talk about the +config.assets.precompile+ option and the default matcher for files: + +<erb> +[ /\w+\.(?!js|css).+/, "application.js", "application.css" ] +</erb> + + +Sprockets also creates a "gzip":http://en.wikipedia.org/wiki/Gzip (.gz) of your assets. This prevents your server from contently compressing your assets for each request. You must configure your server to use gzip compression and serve the compressed assets that will be stored in the public/assets folder. The following are some configuration blocks that you can use for common servers. +NGINX & Apache examples? + + + +h3. Customizing The Pipeline + +h4. CSS + +There is currently one option for processing CSS - SCSS. This Gem extends the CSS syntax and offers minification. + +The following line will enable SCSS in you project. + +<erb> +config.assets.css_compressor = :scss +</erb> + +This option is for compression only and does not relate to the SCSS language extensions that apply when using the +.scss+ file extension on CSS assets. + +h4. Javascript + +There are three options available to process javascript - uglifier, closure and yui. + +The default Gemfile includes "uglifier":https://github.com/lautis/uglifier. 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. + +TODO: Add detail about the other two + +The following line will invoke uglifier for Javascript compression. + +<erb> +config.assets.js_compressor = :uglifier +</erb> + + + +h4. Using your own compressor + +The compressor config settings for CSS and Javascript will also take an Object. + +This object must have a +compress+ method that takes a string as the sole argument and it must return a string. + +<erb> +class Transformer + def compress(string) + do_something_returning_a_string(string) + end +end +</erb> + +To enable this pass a +new+ Object to the config option in +application.rb+: + +<erb> +config.assets.css_compressor = Transformer.new +</erb> + + +h4. Changing the _assets_ path + +The public path that Sprockets uses by default is +/assets+. + +This can be changed to something else: + +<erb> +config.assets.prefix = "/some_other_path" +</erb> + +This is a handy option if you have any existing project (pre Rails 3.1) that already uses this path. + + +h3. Adding Assets to Your Gems + +Assets can also come from external sources in the form of gems. -h4. Directives +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. Stacking Preproccessors +h3. Making Your Library or Gem a Pre-Processor -h3. Packaging Assets with Your Plugin +"You should be able to register [your gems] on Tilt and Sprockets will find them." - Josh +Tilt: https://github.com/rtomayko/tilt -h3. More on Sprockets 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 91827fd493..252003edd0 100644 --- a/railties/guides/source/caching_with_rails.textile +++ b/railties/guides/source/caching_with_rails.textile @@ -112,7 +112,7 @@ As an example, if you wanted to show all the orders placed on your website in re <ruby> <% Order.find_recent.each do |o| %> - <%= o.buyer.name %> bought <% o.product.name %> + <%= o.buyer.name %> bought <%= o.product.name %> <% end %> <% cache do %> @@ -162,17 +162,17 @@ class ProductSweeper < ActionController::Caching::Sweeper # If our sweeper detects that a Product was created call this def after_create(product) - expire_cache_for(product) + expire_cache_for(product) end # If our sweeper detects that a Product was updated call this def after_update(product) - expire_cache_for(product) + expire_cache_for(product) end # If our sweeper detects that a Product was deleted call this def after_destroy(product) - expire_cache_for(product) + expire_cache_for(product) end private @@ -345,7 +345,7 @@ ActionController::Base.cache_store = MyCacheStore.new h4. Cache Keys -The keys used in a cache can be any object that responds to either +:cache_key+ or to +:to_param+. You can implement the +:cache_key+ method on your classes if you need to generate custom keys. ActiveRecord will generate keys based on the class name and record id. +The keys used in a cache can be any object that responds to either +:cache_key+ or to +:to_param+. You can implement the +:cache_key+ method on your classes if you need to generate custom keys. Active Record will generate keys based on the class name and record id. You can use Hashes and Arrays of values as cache keys. @@ -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 ad36c6532e..9e3b25d794 100644 --- a/railties/guides/source/command_line.textile +++ b/railties/guides/source/command_line.textile @@ -5,7 +5,7 @@ Rails comes with every command line tool you'll need to * Create a Rails application * Generate models, controllers, database migrations, and unit tests * Start a development server -* Mess with objects through an interactive shell +* Experiment with objects through an interactive shell * Profile and benchmark your new creation endprologue. @@ -51,21 +51,19 @@ $ 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+ -Let's try it! 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 $ rails server => Booting WEBrick -=> Rails 3.0.0 application starting in development on http://0.0.0.0:3000 +=> Rails 3.1.0 application starting in development on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server [2010-04-18 03:20:33] INFO WEBrick 1.3.1 @@ -75,13 +73,21 @@ $ rails server With just three commands we whipped up a Rails server listening on port 3000. Go to your browser and open "http://localhost:3000":http://localhost:3000, you will see a basic Rails app running. +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] ... ... @@ -97,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: @@ -146,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> @@ -169,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 @@ -182,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 @@ -237,7 +244,7 @@ dependency model create test/unit/high_score_test.rb create test/fixtures/high_scores.yml exists db/migrate - create db/migrate/20081217071914_create_high_scores.rb + create db/migrate/20100209025147_create_high_scores.rb </shell> The generator checks that there exist the directories for models, controllers, helpers, layouts, functional and unit tests, stylesheets, creates the views, controller, model and database migration for HighScore (creating the +high_scores+ table and fields), takes care of the route for the *resource*, and new tests for everything. @@ -267,11 +274,13 @@ h4. +rails console+ The +console+ command lets you interact with your Rails application from the command line. On the underside, +rails console+ uses IRB, so if you've ever used it, you'll be right at home. This is useful for testing out quick ideas with code and changing data server-side without touching the website. +You can also use the alias "c" to invoke the console: <tt>rails c</tt>. + If you wish to test out some code without changing any data, you can do that by invoking +rails console --sandbox+. <shell> $ rails console --sandbox -Loading development environment in sandbox (Rails 3.0.0) +Loading development environment in sandbox (Rails 3.1.0) Any modifications you make will be rolled back on exit irb(main):001:0> </shell> @@ -280,13 +289,11 @@ h4. +rails dbconsole+ +rails dbconsole+ figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL, PostgreSQL, SQLite and SQLite3. -h4. +rails plugin+ +You can also use the alias "db" to invoke the dbconsole: <tt>rails db</tt>. -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? +h4. +rails plugin+ -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 @@ -304,9 +311,15 @@ 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. Believe you-me, the creation of this tutorial used this command many times! +Think of +destroy+ as the opposite of +generate+. It'll figure out what generate did, and undo it. <shell> $ rails generate model Oops @@ -333,9 +346,29 @@ $ rails destroy model Oops notempty app </shell> -h4. +rake about+ +h3. Rake + +Rake is Ruby Make, a standalone Ruby utility that replaces the Unix utility 'make', and uses a 'Rakefile' and +.rake+ files to build up a list of tasks. In Rails, Rake is used for common administration tasks, especially sophisticated ones that build off of each other. -Check it: Version numbers for Ruby, RubyGems, Rails, the Rails subcomponents, your application's folder, the current Rails environment name, your app's database adapter, and schema version! +about+ is useful when you need to ask for help, check if a security patch might affect you, or when you need some stats for an existing Rails installation. +You can get a list of Rake tasks available to you, which will often depend on your current directory, by typing +rake --tasks+. Each task has a description, and should help you find the thing you need. + +<shell> +$ rake --tasks +(in /home/foobar/commandsapp) +rake db:abort_if_pending_migrations # Raises an error if there are pending migrations +rake db:charset # Retrieves the charset for the current environment's database +rake db:collation # Retrieves the collation for the current environment's database +rake db:create # Create the database defined in config/database.yml for the current Rails.env +... +... +rake tmp:pids:clear # Clears all files in tmp/pids +rake tmp:sessions:clear # Clears all files in tmp/sessions +rake tmp:sockets:clear # Clears all files in tmp/sockets +</shell> + +h4. +about+ + +<tt>rake about</tt> gives information about version numbers for Ruby, RubyGems, Rails, the Rails subcomponents, your application's folder, the current Rails environment name, your app's database adapter, and schema version. It is useful when you need to ask for help, check if a security patch might affect you, or when you need some stats for an existing Rails installation. <shell> $ rake about @@ -343,17 +376,98 @@ About your application's environment Ruby version 1.8.7 (x86_64-linux) RubyGems version 1.3.6 Rack version 1.1 -Rails version 3.0.0 -Active Record version 3.0.0 -Action Pack version 3.0.0 -Active Resource version 3.0.0 -Action Mailer version 3.0.0 -Active Support version 3.0.0 +Rails version 3.1.0 +Active Record version 3.1.0 +Action Pack version 3.1.0 +Active Resource version 3.1.0 +Action Mailer version 3.1.0 +Active Support version 3.1.0 Middleware ActionDispatch::Static, Rack::Lock, Rack::Runtime, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::RemoteIp, Rack::Sendfile, ActionDispatch::Callbacks, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::MethodOverride, ActionDispatch::Head Application root /home/foobar/commandsapp Environment development </shell> +h4. +assets+ + +You can precompile the assets in <tt>app/assets</tt> using <tt>rake assets:precompile</tt> and remove compiled assets using <tt>rake assets:clean</tt>. + +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+ + +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+ + ++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+ + ++rake routes+ will list all of your defined routes, which is useful for tracking down routing problems in your app, or giving you a good overview of the URLs in an app you're trying to get familiar with. + +h4. +test+ + +INFO: A good description of unit testing in Rails is given in "A Guide to Testing Rails Applications":testing.html + +Rails comes with a test suite called Test::Unit. It is through the use of tests that Rails itself is so stable, and the slew of people working on Rails can prove that everything works as it should. + +The +test:+ namespace helps in running the different tests you will (hopefully!) write. + +h4. +tmp+ + +The <tt>Rails.root/tmp</tt> directory is, like the *nix /tmp directory, the holding place for temporary files like sessions (if you're using a file store for files), process id files, and cached actions. The +tmp:+ namespace tasks will help you clear them if you need to if they've become overgrown, or create them in case of deletions gone awry. + +h4. Miscellaneous + +* +rake stats+ is great for looking at statistics on your code, displaying things like KLOCs (thousands of lines of code) and your code to test ratio. +* +rake secret+ will give you a pseudo-random key to use for your session secret. +* <tt>rake time:zones:all</tt> lists all the timezones Rails knows about. + h3. The Rails Advanced Command Line More advanced use of the command line is focused around finding useful (even surprising at times) options in the utilities, and fitting those to your needs and specific work flow. Listed here are some tricks up Rails' sleeve. @@ -393,7 +507,7 @@ We had to create the *gitapp* directory and initialize an empty git repository b <shell> $ cat config/database.yml -# PostgreSQL. Versions 7.4 and 8.x are supported. +# PostgreSQL. Versions 8.2 and up are supported. # # Install the ruby-postgres driver: # gem install ruby-postgres @@ -414,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. @@ -437,157 +553,6 @@ Successfully installed mongrel-1.1.5 Installing RDoc documentation for mongrel-1.1.5... $ rails server mongrel => Booting Mongrel (use 'rails server webrick' to force WEBrick) -=> Rails 3.0.0 application starting on http://0.0.0.0:3000 +=> Rails 3.1.0 application starting on http://0.0.0.0:3000 ... </shell> - -h4. The Rails Generation: Generators - -INFO: For a good rundown on generators, see "Understanding Generators":http://wiki.rubyonrails.org/rails/pages/UnderstandingGenerators. A lot of its material is presented here. - -Generators are code that generates code. Let's experiment by building one. Our generator will generate a text file. - -The Rails generator by default looks in these places for available generators, where Rails.root is the root of your Rails application, like /home/foobar/commandsapp: - -* Rails.root/lib/generators -* Rails.root/vendor/generators -* Inside any plugin with a directory like "generators" or "rails_generators" -* ~/.rails/generators -* Inside any Gem you have installed with a name ending in "_generator" -* Inside any Gem installed with a "rails_generators" path, and a file ending in "_generator.rb" -* Finally, the builtin Rails generators (controller, model, mailer, etc.) - -Let's try the fourth option (in our home directory), which will be easy to clean up later: - -<shell> -$ mkdir -p ~/.rails/generators/tutorial_test/templates -$ touch ~/.rails/generators/tutorial_test/templates/tutorial.erb -$ touch ~/.rails/generators/tutorial_test/tutorial_test_generator.rb -</shell> - -We'll fill +tutorial_test_generator.rb+ out with: - -<ruby> -class TutorialTestGenerator < Rails::Generator::Base - def initialize(*runtime_args) - super(*runtime_args) - @tut_args = runtime_args - end - - def manifest - record do |m| - m.directory "public" - m.template "tutorial.erb", File.join("public", "tutorial.txt"), - :assigns => { :args => @tut_args } - end - end -end -</ruby> - -We take whatever args are supplied, save them to an instance variable, and literally copying from the Rails source, implement a +manifest+ method, which calls +record+ with a block, and we: - -* Check there's a *public* directory. You bet there is. -* Run the ERB template called "tutorial.erb". -* Save it into "Rails.root/public/tutorial.txt". -* Pass in the arguments we saved through the +:assigns+ parameter. - -Next we'll build the template: - -<shell> -$ cat ~/.rails/generators/tutorial_test/templates/tutorial.erb -I'm a template! - -I got assigned some args: -<%= require 'pp'; PP.pp(args, "") %> -</shell> - -Then we'll make sure it got included in the list of available generators: - -<shell> -$ rails generate -... -... -Installed Generators - User: tutorial_test -</shell> - -SWEET! Now let's generate some text, yeah! - -<shell> -$ rails generate tutorial_test arg1 arg2 arg3 - exists public - create public/tutorial.txt -</shell> - -And the result: - -<shell> -$ cat public/tutorial.txt -I'm a template! - -I got assigned some args: -[["arg1", "arg2", "arg3"], - {:collision=>:ask, - :quiet=>false, - :generator=>"tutorial_test", - :command=>:create}] -</shell> - -Tada! - -h4. Rake is Ruby Make - -Rake is a standalone Ruby utility that replaces the Unix utility 'make', and uses a 'Rakefile' and +.rake+ files to build up a list of tasks. In Rails, Rake is used for common administration tasks, especially sophisticated ones that build off of each other. - -You can get a list of Rake tasks available to you, which will often depend on your current directory, by typing +rake --tasks+. Each task has a description, and should help you find the thing you need. - -<shell> -$ rake --tasks -(in /home/foobar/commandsapp) -rake db:abort_if_pending_migrations # Raises an error if there are pending migrations -rake db:charset # Retrieves the charset for the current environment's database -rake db:collation # Retrieves the collation for the current environment's database -rake db:create # Create the database defined in config/database.yml for the current Rails.env -... -... -rake tmp:pids:clear # Clears all files in tmp/pids -rake tmp:sessions:clear # Clears all files in tmp/sessions -rake tmp:sockets:clear # Clears all files in tmp/sockets -</shell> - -h5. +db:+ Database - -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. - -h5. +doc:+ Documentation - -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. - -h5. +notes:+ Code note enumeration - -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. - -h5. +test:+ Rails tests - -INFO: A good description of unit testing in Rails is given in "A Guide to Testing Rails Applications":testing.html - -Rails comes with a test suite called Test::Unit. It is through the use of tests that Rails itself is so stable, and the slew of people working on Rails can prove that everything works as it should. - -The +test:+ namespace helps in running the different tests you will (hopefully!) write. - -h5. +time:+ Timezones - -You can list all the timezones Rails knows about with +rake time:zones:all+, which is useful just in day-to-day life. - -h5. +tmp:+ Temporary files - -The tmp directory is, like in the *nix /tmp directory, the holding place for temporary files like sessions (if you're using a file store for files), process id files, and cached actions. The +tmp:+ namespace tasks will help you clear them if you need to if they've become overgrown, or create them in case of deletions gone awry. - -h5. Miscellaneous Tasks - - +rake stats+ is great for looking at statistics on your code, displaying things like KLOCs (thousands of lines of code) and your code to test ratio. - - +rake secret+ will give you a pseudo-random key to use for your session secret. - - +rake routes+ will list all of your defined routes, which is useful for tracking down routing problems in your app, or giving you a good overview of the URLs in an app you're trying to get familiar with. - diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile index 135c849ec3..8e6010ff79 100644 --- a/railties/guides/source/configuring.textile +++ b/railties/guides/source/configuring.textile @@ -18,7 +18,7 @@ Rails offers four standard spots to place initialization code: h3. Running Code Before Rails -In the rare event that your application needs to run some code before Rails itself is loaded, put it above the call to +require 'rails/all'+ in your +config/application.rb+. +In the rare event that your application needs to run some code before Rails itself is loaded, put it above the call to +require 'rails/all'+ in +config/application.rb+. h3. Configuring Rails Components @@ -52,12 +52,14 @@ end * +config.asset_host+ sets the host for the assets. Useful when CDNs are used for hosting assets, or when you want to work around the concurrency constraints builtin in browsers using different domain aliases. Shorter version of +config.action_controller.asset_host+. -* +config.asset_path+ can take a callable, a string, or be +nil+. Default is +nil+. If set, this configuration parameter let's you decorate asset paths. For example, the normal path for +blog.js+ would be +/javascripts/blog.js+, let that absolute path be +path+. If +config.asset_path+ is a callable, Rails calls it when generating asset paths passing +path+ as argument. If +config.asset_path+ is a string, it is expected to be a +sprintf+ format string with a +%s+ where +path+ will get inserted. In either case, Rails outputs the decorated path. *This option is ignored if the asset pipeline is enabled, which is by default*. Shorter version of +config.action_controller.asset_path+. +* +config.asset_path+ lets you decorate asset paths. This can be a callable, a string, or be +nil+ which is the default. For example, the normal path for +blog.js+ would be +/javascripts/blog.js+, let that absolute path be +path+. If +config.asset_path+ is a callable, Rails calls it when generating asset paths passing +path+ as argument. If +config.asset_path+ is a string, it is expected to be a +sprintf+ format string with a +%s+ where +path+ will get inserted. In either case, Rails outputs the decorated path. Shorter version of +config.action_controller.asset_path+. <ruby> config.asset_path = proc { |path| "/blog/public#{path}" } </ruby> +NOTE. The +config.asset_path+ configuration is ignored if the asset pipeline is enabled, which is the default. + * +config.autoload_once_paths+ accepts an array of paths from which Rails will autoload constants that won't be wiped per request. Relevant if +config.cache_classes+ is false, which is the case in development mode by default. Otherwise, all autoloading happens only once. All elements of this array must also be in +autoload_paths+. Default is an empty array. * +config.autoload_paths+ accepts an array of paths from which Rails will autoload constants. Default is all directories under +app+. @@ -84,11 +86,11 @@ config.asset_path = proc { |path| "/blog/public#{path}" } * +config.log_level+ defines the verbosity of the Rails logger. This option defaults to +:debug+ for all modes except production, where it defaults to +:info+. -* +config.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ +Logger+ class. Defaults to an instance of +ActiveSupport::BufferedLogger+, with auto flushing off in production mode. +* +config.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby +Logger+ class. Defaults to an instance of +ActiveSupport::BufferedLogger+, with auto flushing off in production mode. -* +config.middleware+ allows you to configure the application's middleware. This is covered in depth in the "Configuring Middleware":configuring-middleware section below. +* +config.middleware+ allows you to configure the application's middleware. This is covered in depth in the "Configuring Middleware":#configuring-middleware section below. -* +config.plugins+ accepts the list of plugins to load. If this is set to +nil+, default, all plugins will be loaded. If this is set to +[]+, no plugins will be loaded. Otherwise, plugins will be loaded in the order specified. This option let's you enforce some particular loading order, useful when dependencies between plugins require it. For that use case, put first the plugins you want to be loaded in a certain order, and then the special symbol +:all+ to have the rest loaded without the need to specify them. +* +config.plugins+ accepts the list of plugins to load. The default is +nil+ in which case all plugins will be loaded. If this is set to +[]+, no plugins will be loaded. Otherwise, plugins will be loaded in the order specified. This option lets you enforce some particular loading order, useful when dependencies between plugins require it. For that use case, put first the plugins you want to be loaded in a certain order, and then the special symbol +:all+ to have the rest loaded without the need to specify them. * +config.preload_frameworks+ enables or disables preloading all frameworks at startup. Enabled by +config.threadsafe!+. Defaults to +nil+, so is disabled. @@ -96,9 +98,9 @@ config.asset_path = proc { |path| "/blog/public#{path}" } * +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+, default, +:mem_cache_store+, and +:disabled+. The last one tells Rails not to deal with sessions. Custom session stores can also be specified like so: +* +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: <ruby> config.session_store :my_custom_store @@ -114,52 +116,71 @@ 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: <ruby> - config.generators do |g| - g.orm :active_record - g.test_framework :test_unit - end +config.generators do |g| + g.orm :active_record + g.test_framework :test_unit +end </ruby> The full set of methods that can be used in this block are as follows: -* +force_plural+ allows pluralized model names. Defaults to _false_. -* +helper+ defines whether or not to generate helpers. Defaults to _true_. -* +orm+ defines which orm to use. Defaults to _nil_, so will use Active Record by default. -* +integration_tool+ defines which integration tool to use. Defaults to _nil_. -* +performance_tool+ defines which performance tool to use. Defaults to _nil_. +* +assets+ allows to create assets on generating a scaffold. Defaults to +true+. +* +force_plural+ allows pluralized model names. Defaults to +false+. +* +helper+ defines whether or not to generate helpers. Defaults to +true+. +* +integration_tool+ defines which integration tool to use. Defaults to +nil+. +* +javascripts+ turns on the hook for javascripts in generators. Used in Rails for when the +scaffold+ generator is ran. Defaults to +true+. +* +javascript_engine+ configures the engine to be used (for eg. coffee) when generating assets. Defaults to +nil+. +* +orm+ defines which orm to use. Defaults to +false+ and will use Active Record by default. +* +performance_tool+ defines which performance tool to use. Defaults to +nil+. * +resource_controller+ defines which generator to use for generating a controller when using +rails generate resource+. Defaults to +:controller+. * +scaffold_controller+ different from +resource_controller+, defines which generator to use for generating a _scaffolded_ controller when using +rails generate scaffold+. Defaults to +:scaffold_controller+. -* +stylesheets+ turns on the hook for stylesheets in generators. Used in Rails for when the +scaffold+ generator is ran, but this hook can be used in other generates as well. -* +test_framework+ defines which test framework to use. Defaults to _nil_, so will use Test::Unit by default. +* +stylesheets+ turns on the hook for stylesheets in generators. Used in Rails for when the +scaffold+ generator is ran, but this hook can be used in other generates as well. Defaults to +true+. +* +stylesheet_engine+ configures the stylesheet engine (for eg. sass) to be used when generating assets. Defaults to +:css+. +* +test_framework+ defines which test framework to use. Defaults to +false+ and will use Test::Unit by default. * +template_engine+ defines which template engine to use, such as ERB or Haml. Defaults to +:erb+. h4. Configuring Middleware Every Rails application comes with a standard set of middleware which it uses in this order in the development environment: -* +Rack::SSL+ Will force every request to be under HTTPS protocol. Will be available if +config.force_ssl+ is set to _true_. -* +ActionDispatch::Static+ is used to serve static assets. Disabled if +config.serve_static_assets+ is _true_. -* +Rack::Lock+ Will wrap the app in mutex so it can only be called by a single thread at a time. Only enabled if +config.action_controller.allow_concurrency+ is set to _false_, which it is by default. +* +Rack::SSL+ Will force every request to be under HTTPS protocol. Will be available if +config.force_ssl+ is set to +true+. +* +ActionDispatch::Static+ is used to serve static assets. Disabled if +config.serve_static_assets+ is +true+. +* +Rack::Lock+ Will wrap the app in mutex so it can only be called by a single thread at a time. Only enabled if +config.action_controller.allow_concurrency+ is set to +false+, which it is by default. * +ActiveSupport::Cache::Strategy::LocalCache+ Serves as a basic memory backed cache. This cache is not thread safe and is intended only for serving as a temporary memory cache for a single thread. * +Rack::Runtime+ Sets an +X-Runtime+ header, containing the time (in seconds) taken to execute the request. -* +Rails::Rack::Logger+ Will notify the logs that the request has began. After request is complete, flushes all the logs. -* +ActionDispatch::ShowExceptions+ rescues any exception returned by the application and renders nice exception pages if the request is local or if +config.consider_all_requests_local+ is set to _true_. If +config.action_dispatch.show_exceptions+ is set to _false_, exceptions will be raised regardless. -* +ActionDispatch::RemoteIp+ checks for IP spoofing attacks. Configurable with the +config.action_dispatch.ip_spoofing_check+ and +config.action_dispatch.trusted_proxies+ settings. -* +Rack::Sendfile+ The Sendfile middleware intercepts responses whose body is being served from a file and replaces it with a server specific X-Sendfile header. Configurable with +config.action_dispatch.x_sendfile_header+ +* +Rails::Rack::Logger+ Notifies the logs that the request has began. After request is complete, flushes all the logs. +* +ActionDispatch::ShowExceptions+ Rescues any exception returned by the application and renders nice exception pages if the request is local or if +config.consider_all_requests_local+ is set to +true+. If +config.action_dispatch.show_exceptions+ is set to +false+, exceptions will be raised regardless. +* +ActionDispatch::RemoteIp+ Checks for IP spoofing attacks. Configurable with the +config.action_dispatch.ip_spoofing_check+ and +config.action_dispatch.trusted_proxies+ settings. +* +Rack::Sendfile+ Intercepts responses whose body is being served from a file and replaces it with a server specific X-Sendfile header. Configurable with +config.action_dispatch.x_sendfile_header+. * +ActionDispatch::Callbacks+ Runs the prepare callbacks before serving the request. -* +ActiveRecord::ConnectionAdapters::ConnectionManagement+ cleans active connections after each request, unless the +rack.test+ key in the request environment is set to _true_. -* +ActiveRecord::QueryCache+ caches all +SELECT+ queries generated in a request. If an +INSERT+ or +UPDATE+ takes place then the cache is cleaned. +* +ActiveRecord::ConnectionAdapters::ConnectionManagement+ cleans active connections after each request, unless the +rack.test+ key in the request environment is set to +true+. +* +ActiveRecord::QueryCache+ caches all SELECT queries generated in a request. If any INSERT or UPDATE takes place then the cache is cleaned. * +ActionDispatch::Cookies+ sets cookies for the request. * +ActionDispatch::Session::CookieStore+ is responsible for storing the session in cookies. An alternate middleware can be used for this by changing the +config.action_controller.session_store+ to an alternate value. Additionally, options passed to this can be configured by using +config.action_controller.session_options+. * +ActionDispatch::Flash+ sets up the +flash+ keys. Only available if +config.action_controller.session_store+ is set to a value. -* +ActionDispatch::ParamsParser+ parses out parameters from the request into +params+ +* +ActionDispatch::ParamsParser+ parses out parameters from the request into +params+. * +Rack::MethodOverride+ allows the method to be overridden if +params[:_method]+ is set. This is the middleware which supports the PUT and DELETE HTTP method types. * +ActionDispatch::Head+ converts HEAD requests to GET requests and serves them as so. * +ActionDispatch::BestStandardsSupport+ enables "best standards support" so that IE8 renders some elements correctly. @@ -167,44 +188,44 @@ Every Rails application comes with a standard set of middleware which it uses in Besides these usual middleware, you can add your own by using the +config.middleware.use+ method: <ruby> - config.middleware.use Magical::Unicorns +config.middleware.use Magical::Unicorns </ruby> -This will put the +Magical::Unicorns+ middleware on the end of the stack. If you wish to put this middleware before another use +insert_before+: +This will put the +Magical::Unicorns+ middleware on the end of the stack. You can use +insert_before+ if you wish to add a middleware before another. <ruby> - config.middleware.insert_before ActionDispatch::Head, Magical::Unicorns +config.middleware.insert_before ActionDispatch::Head, Magical::Unicorns </ruby> -There's also +insert_after+ which will insert a middleware _after_ another: +There's also +insert_after+ which will insert a middleware after another: <ruby> - config.middleware.insert_after ActionDispatch::Head, Magical::Unicorns +config.middleware.insert_after ActionDispatch::Head, Magical::Unicorns </ruby> Middlewares can also be completely swapped out and replaced with others: <ruby> - config.middleware.swap ActionDispatch::BestStandardsSupport, Magical::Unicorns +config.middleware.swap ActionDispatch::BestStandardsSupport, Magical::Unicorns </ruby> They can also be removed from the stack completely: <ruby> - config.middleware.delete ActionDispatch::BestStandardsSupport +config.middleware.delete ActionDispatch::BestStandardsSupport </ruby> h4. Configuring i18n * +config.i18n.default_locale+ sets the default locale of an application used for i18n. Defaults to +:en+. -* +config.i18n.load_path+ sets the path Rails uses to look for locale files. Defaults to +config/locales/*.{yml,rb}+ +* +config.i18n.load_path+ sets the path Rails uses to look for locale files. Defaults to +config/locales/*.{yml,rb}+. h4. Configuring Active Record <tt>config.active_record</tt> includes a variety of configuration options: -* +config.active_record.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8.x Logger class, which is then passed on to any new database connections made. You can retrieve this logger by calling +logger+ on either an Active Record model class or an Active Record model instance. Set to nil to disable logging. +* +config.active_record.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then passed on to any new database connections made. You can retrieve this logger by calling +logger+ on either an Active Record model class or an Active Record model instance. Set to +nil+ to disable logging. * +config.active_record.primary_key_prefix_type+ lets you adjust the naming for primary key columns. By default, Rails assumes that primary key columns are named +id+ (and this configuration option doesn't need to be set.) There are two other choices: ** +:table_name+ would make the primary key for the Customer class +customerid+ @@ -214,21 +235,21 @@ h4. Configuring Active Record * +config.active_record.table_name_suffix+ lets you set a global string to be appended to table names. If you set this to +_northwest+, then the Customer class will look for +customers_northwest+ as its table. The default is an empty string. -* +config.active_record.pluralize_table_names+ specifies whether Rails will look for singular or plural table names in the database. If set to +true+ (the default), then the Customer class will use the +customers+ table. If set to +false+, then the Customers class will use the +customer+ table. +* +config.active_record.pluralize_table_names+ specifies whether Rails will look for singular or plural table names in the database. If set to true (the default), then the Customer class will use the +customers+ table. If set to false, then the Customer class will use the +customer+ table. -* +config.active_record.default_timezone+ determines whether to use +Time.local+ (if set to +:local+) or +Time.utc+ (if set to +:utc+) when pulling dates and times from the database. The default is +:utc+ for Rails, although ActiveRecord defaults to +:local+ when used outside of Rails. +* +config.active_record.default_timezone+ determines whether to use +Time.local+ (if set to +:local+) or +Time.utc+ (if set to +:utc+) when pulling dates and times from the database. The default is +:utc+ for Rails, although Active Record defaults to +:local+ when used outside of Rails. * +config.active_record.schema_format+ controls the format for dumping the database schema to a file. The options are +:ruby+ (the default) for a database-independent version that depends on migrations, or +:sql+ for a set of (potentially database-dependent) SQL statements. -* +config.active_record.timestamped_migrations+ controls whether migrations are numbered with serial integers or with timestamps. The default is +true+, to use timestamps, which are preferred if there are multiple developers working on the same application. +* +config.active_record.timestamped_migrations+ controls whether migrations are numbered with serial integers or with timestamps. The default is true, to use timestamps, which are preferred if there are multiple developers working on the same application. -* +config.active_record.lock_optimistically+ controls whether ActiveRecord will use optimistic locking. By default this is +true+. +* +config.active_record.lock_optimistically+ controls whether Active Record will use optimistic locking and is true by default. * +config.active_record.whitelist_attributes+ will create an empty whitelist of attributes available for mass-assignment security for all models in your app. The MySQL adapter adds one additional configuration option: -* +ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans+ controls whether ActiveRecord will consider all +tinyint(1)+ columns in a MySQL database to be booleans. By default this is +true+. +* +ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans+ controls whether Active Record will consider all +tinyint(1)+ columns in a MySQL database to be booleans and is true by default. The schema dumper adds one additional configuration option: @@ -244,17 +265,17 @@ h4. Configuring Action Controller * +config.action_controller.page_cache_directory+ should be the document root for the web server and is set using <tt>Base.page_cache_directory = "/document/root"</tt>. For Rails, this directory has already been set to +Rails.public_path+ (which is usually set to <tt>Rails.root + "/public"</tt>). Changing this setting can be useful to avoid naming conflicts with files in <tt>public/</tt>, but doing so will likely require configuring your web server to look in the new location for cached files. -* +config.action_controller.page_cache_extension+ configures the extension used for cached pages saved to +page_cache_directory+. Defaults to +.html+ +* +config.action_controller.page_cache_extension+ configures the extension used for cached pages saved to +page_cache_directory+. Defaults to +.html+. -* +config.action_controller.perform_caching+ configures whether the application should perform caching or not. Set to _false_ in development mode, _true_ in production. +* +config.action_controller.perform_caching+ configures whether the application should perform caching or not. Set to false in development mode, true in production. * +config.action_controller.default_charset+ specifies the default character set for all renders. The default is "utf-8". -* +config.action_controller.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Action Controller. Set to nil to disable logging. +* +config.action_controller.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action Controller. Set to +nil+ to disable logging. * +config.action_controller.request_forgery_protection_token+ sets the token parameter name for RequestForgery. Calling +protect_from_forgery+ sets it to +:authenticity_token+ by default. -* +config.action_controller.allow_forgery_protection+ enables or disables CSRF protection. By default this is +false+ in test mode and +true+ in all other modes. +* +config.action_controller.allow_forgery_protection+ enables or disables CSRF protection. By default this is false in test mode and true in all other modes. * +config.action_controller.relative_url_root+ can be used to tell Rails that you are deploying to a subdirectory. The default is +ENV['RAILS_RELATIVE_URL_ROOT']+. @@ -288,45 +309,49 @@ h4. Configuring Action View There are only a few configuration options for Action View, starting with four on +ActionView::Base+: -* +config.action_view.field_error_proc+ provides an HTML generator for displaying errors that come from Active Record. The default is <tt>Proc.new{ |html_tag, instance| %Q(%<div class="field_with_errors">#{html_tag}</div>).html_safe }</tt> +* +config.action_view.field_error_proc+ provides an HTML generator for displaying errors that come from Active Record. The default is + +<ruby> +Proc.new { |html_tag, instance| %Q(<div class="field_with_errors">#{html_tag}</div>).html_safe } +</ruby> * +config.action_view.default_form_builder+ tells Rails which form builder to use by default. The default is +ActionView::Helpers::FormBuilder+. -* +config.action_view.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Action Mailer. Set to nil to disable logging. +* +config.action_view.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action Mailer. Set to +nil+ to disable logging. * +config.action_view.erb_trim_mode+ gives the trim mode to be used by ERB. It defaults to +'-'+. See the "ERB documentation":http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/ for more information. * +config.action_view.javascript_expansions+ is a hash containing expansions that can be used for the JavaScript include tag. By default, this is defined as: <ruby> - config.action_view.javascript_expansions = { :defaults => ['prototype', 'effects', 'dragdrop', 'controls', 'rails'] } +config.action_view.javascript_expansions = { :defaults => %w(jquery jquery_ujs) } </ruby> However, you may add to this by defining others: <ruby> - config.action_view.javascript_expansions[:jquery] = ["jquery", "jquery-ui"] +config.action_view.javascript_expansions[:prototype] = ['prototype', 'effects', 'dragdrop', 'controls'] </ruby> And can reference in the view with the following code: <ruby> - <%= javascript_include_tag :jquery %> +<%= javascript_include_tag :prototype %> </ruby> * +config.action_view.stylesheet_expansions+ works in much the same way as +javascript_expansions+, but has no default key. Keys defined for this hash can be referenced in the view like such: <ruby> - <%= stylesheet_link_tag :special %> +<%= stylesheet_link_tag :special %> </ruby> -* +ActionView::Helpers::AssetTagHelper::AssetPaths.cache_asset_ids+ With the cache enabled, the asset tag helper methods will make fewer expensive file system calls (the default implementation checks the file system timestamp). However this prevents you from modifying any asset files while the server is running. +* +config.action_view.cache_asset_ids+ With the cache enabled, the asset tag helper methods will make fewer expensive file system calls (the default implementation checks the file system timestamp). However this prevents you from modifying any asset files while the server is running. h4. Configuring Action Mailer There are a number of settings available on +config.action_mailer+: -* +config.action_mailer.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Action Mailer. Set to nil to disable logging. +* +config.action_mailer.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action Mailer. Set to +nil+ to disable logging. * +config.action_mailer.smtp_settings+ allows detailed configuration for the +:smtp+ delivery method. It accepts a hash of options, which can include any of these options: ** +:address+ - Allows you to use a remote mail server. Just change it from its default "localhost" setting. @@ -340,25 +365,35 @@ There are a number of settings available on +config.action_mailer+: ** +:location+ - The location of the sendmail executable. Defaults to +/usr/sbin/sendmail+. ** +:arguments+ - The command line arguments. Defaults to +-i -t+. -* +config.action_mailer.raise_delivery_errors+ specifies whether to raise an error if email delivery cannot be completed. It defaults to +true+. +* +config.action_mailer.raise_delivery_errors+ specifies whether to raise an error if email delivery cannot be completed. It defaults to true. * +config.action_mailer.delivery_method+ defines the delivery method. The allowed values are +:smtp+ (default), +:sendmail+, and +:test+. -* +config.action_mailer.perform_deliveries+ specifies whether mail will actually be delivered. By default this is +true+; it can be convenient to set it to +false+ for testing. +* +config.action_mailer.perform_deliveries+ specifies whether mail will actually be delivered and is true by default. It can be convenient to set it to false for testing. * +config.action_mailer.default+ configures Action Mailer defaults. These default to: <ruby> - :mime_version => "1.0", - :charset => "UTF-8", - :content_type => "text/plain", - :parts_order => [ "text/plain", "text/enriched", "text/html" ] +:mime_version => "1.0", +:charset => "UTF-8", +:content_type => "text/plain", +:parts_order => [ "text/plain", "text/enriched", "text/html" ] +</ruby> + +* +config.action_mailer.observers+ registers observers which will be notified when mail is delivered. +<ruby> +config.action_mailer.observers = ["MailObserver"] +</ruby> + +* +config.action_mailer.interceptors+ registers interceptors which will be called before mail is sent. +<ruby> +config.action_mailer.interceptors = ["MailInterceptor"] </ruby> h4. Configuring Active Resource There is a single configuration setting available on +config.active_resource+: -* +config.active_resource.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Active Resource. Set to nil to disable logging. +* +config.active_resource.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Active Resource. Set to +nil+ to disable logging. h4. Configuring Active Support @@ -398,7 +433,7 @@ Some parts of Rails can also be configured externally by supplying environment v h3. Using Initializer Files -After loading the framework and any gems and plugins in your application, Rails turns to loading initializers. An initializer is any file of Ruby code stored under +config/initializers+ in your application. You can use initializers to hold configuration settings that should be made after all of the frameworks, plugins and gems are loaded, such as options to configure settings for these parts. +After loading the framework and any gems and plugins in your application, Rails turns to loading initializers. An initializer is any Ruby file stored under +config/initializers+ in your application. You can use initializers to hold configuration settings that should be made after all of the frameworks, plugins and gems are loaded, such as options to configure settings for these parts. NOTE: You can use subfolders to organize your initializers if you like, because Rails will look into the whole file hierarchy from the initializers folder on down. @@ -406,7 +441,7 @@ TIP: If you have any ordering dependency in your initializers, you can control t h3. Initialization events -Rails has 5 initialization events which can be hooked into (listed in order that they are ran): +Rails has 5 initialization events which can be hooked into (listed in the order that they are ran): * +before_configuration+: This is run as soon as the application constant inherits from +Rails::Application+. The +config+ calls are evaluated before this happens. @@ -437,7 +472,7 @@ Initializers defined using the +initializer+ method will be ran in the order the WARNING: You may put your initializer before or after any other initializer in the chain, as long as it is logical. Say you have 4 initializers called "one" through "four" (defined in that order) and you define "four" to go _before_ "four" but _after_ "three", that just isn't logical and Rails will not be able to determine your initializer order. -The block's argument of the +initialize+ is the instance of the application itself, and so we can access the configuration on it by using the +config+ method as this initializer does. +The block argument of the +initializer+ method is the instance of the application itself, and so we can access the configuration on it by using the +config+ method as done in the example. Because +Rails::Application+ inherits from +Rails::Railtie+ (indirectly), you can use the +initializer+ method in +config/application.rb+ to define initializers for the application. @@ -450,21 +485,21 @@ Serves as a placeholder so that +:load_environment_config+ can be defined to run *+load_active_support+* Requires +active_support/dependencies+ which sets up the basis for Active Support. Optionally requires +active_support/all+ if +config.active_support.bare+ is un-truthful, which is the default. -*+preload_frameworks+* Will load all autoload dependencies of Rails automatically if +config.preload_frameworks+ is +true+ or "truthful". By default this configuration option is disabled. In Rails, when internal classes are referenced for the first time they are autoloaded. +:preload_frameworks+ loads all of this at once on initialization. +*+preload_frameworks+* Loads all autoload dependencies of Rails automatically if +config.preload_frameworks+ is +true+ or "truthful". By default this configuration option is disabled. In Rails, when internal classes are referenced for the first time they are autoloaded. +:preload_frameworks+ loads all of this at once on initialization. -*+initialize_logger+* Initializes the logger (an +ActiveSupport::BufferedLogger+ object) for the application and makes it accessible at +Rails.logger+, providing that there's no initializer inserted before this point that has defined +Rails.logger+. +*+initialize_logger+* Initializes the logger (an +ActiveSupport::BufferedLogger+ object) for the application and makes it accessible at +Rails.logger+, provided that no initializer inserted before this point has defined +Rails.logger+. -*+initialize_cache+* If +RAILS_CACHE+ isn't yet set, initializes the cache by referencing the value in +config.cache_store+ and stores the outcome as +RAILS_CACHE+. If this object responds to the +middleware+ method, its middleware is inserted before +Rack::Runtime+ in the middleware stack. +*+initialize_cache+* If +RAILS_CACHE+ isn't set yet, initializes the cache by referencing the value in +config.cache_store+ and stores the outcome as +RAILS_CACHE+. If this object responds to the +middleware+ method, its middleware is inserted before +Rack::Runtime+ in the middleware stack. *+set_clear_dependencies_hook+* Provides a hook for +active_record.set_dispatch_hooks+ to use, which will run before this initializer. This initializer -- which runs only if +cache_classes+ is set to +false+ -- uses +ActionDispatch::Callbacks.after+ to remove the constants which have been referenced during the request from the object space so that they will be reloaded during the following request. -*+initialize_dependency_mechanism+* If +config.cache_classes+ is set to +true+, configures +ActiveSupport::Dependencies.mechanism+ to +require+ dependencies rather than +load+ them. +*+initialize_dependency_mechanism+* If +config.cache_classes+ is true, configures +ActiveSupport::Dependencies.mechanism+ to +require+ dependencies rather than +load+ them. *+bootstrap_hook+* Runs all configured +before_initialize+ blocks. *+i18n.callbacks+* In the development environment, sets up a +to_prepare+ callback which will call +I18n.reload!+ if any of the locales have changed since the last request. In production mode this callback will only run on the first request. -*+active_support.initialize_whiny_nils+* Will require +active_support/whiny_nil+ if +config.whiny_nils+ is set to +true+. This file will output errors such as: +*+active_support.initialize_whiny_nils+* Requires +active_support/whiny_nil+ if +config.whiny_nils+ is true. This file will output errors such as: <plain> Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id @@ -480,19 +515,19 @@ The error occurred while evaluating nil.each *+active_support.deprecation_behavior+* Sets up deprecation reporting for environments, defaulting to +:log+ for development, +:notify+ for production and +:stderr+ for test. If a value isn't set for +config.active_support.deprecation+ then this initializer will prompt the user to configure this line in the current environment's +config/environments+ file. Can be set to an array of values. -*+active_support.initialize_time_zone+* Sets the default time zone for the application based off the +config.time_zone+ setting, which defaults to "UTC". +*+active_support.initialize_time_zone+* Sets the default time zone for the application based on the +config.time_zone+ setting, which defaults to "UTC". *+action_dispatch.configure+* Configures the +ActionDispatch::Http::URL.tld_length+ to be set to the value of +config.action_dispatch.tld_length+. -*+action_view.cache_asset_ids+* Will set +ActionView::Helpers::AssetTagHelper::AssetPaths.cache_asset_ids+ to +false+ when Active Support loads, but only if +config.cache_classes+ is too. +*+action_view.cache_asset_ids+* Sets +ActionView::Helpers::AssetTagHelper::AssetPaths.cache_asset_ids+ to +false+ when Active Support loads, but only if +config.cache_classes+ is too. *+action_view.javascript_expansions+* Registers the expansions set up by +config.action_view.javascript_expansions+ and +config.action_view.stylesheet_expansions+ to be recognised by Action View and therefore usable in the views. *+action_view.set_configs+* Sets up Action View by using the settings in +config.action_view+ by +send+'ing the method names as setters to +ActionView::Base+ and passing the values through. -*+action_controller.logger+* Sets +ActionController::Base.logger+ -- if it's not already set -- to +Rails.logger+. +*+action_controller.logger+* Sets +ActionController::Base.logger+ -- if it's not already set -- to +Rails.logger+. -*+action_controller.initialize_framework_caches+* Sets +ActionController::Base.cache_store+ -- if it's not already set -- to +RAILS_CACHE+. +*+action_controller.initialize_framework_caches+* Sets +ActionController::Base.cache_store+ -- if it's not already set -- to +RAILS_CACHE+. *+action_controller.set_configs+* Sets up Action Controller by using the settings in +config.action_controller+ by +send+'ing the method names as setters to +ActionController::Base+ and passing the values through. @@ -506,9 +541,9 @@ The error occurred while evaluating nil.each *+active_record.initialize_database+* Loads the database configuration (by default) from +config/database.yml+ and establishes a connection for the current environment. -*+active_record.log_runtime+* Includes +ActiveRecord::Railties::ControllerRuntime+ which is responsible for reporting the length of time Active Record calls took for the request back to the logger. +*+active_record.log_runtime+* Includes +ActiveRecord::Railties::ControllerRuntime+ which is responsible for reporting the time taken by Active Record calls for the request back to the logger. -*+active_record.set_dispatch_hooks+* If +config.cache_classes+ is set to false, all reloadable connections to the database will be reset. +*+active_record.set_dispatch_hooks+* Resets all reloadable connections to the database if +config.cache_classes+ is set to +false+. *+action_mailer.logger+* Sets +ActionMailer::Base.logger+ -- if it's not already set -- to +Rails.logger+. @@ -536,10 +571,6 @@ The error occurred while evaluating nil.each *+load_config_initializers+* Loads all Ruby files from +config/initializers+ in the application, railties and engines. The files in this directory can be used to hold configuration settings that should be made after all of the frameworks and plugins are loaded. -NOTE: You can use subfolders to organize your initializers if you like, because Rails will look into the whole file hierarchy from the +initializers+ folder on down. - -TIP: If you have any ordering dependency in your initializers, you can control the load order by naming. For example, +01_critical.rb+ will be loaded before +02_normal.rb+. - *+engines_blank_point+* Provides a point-in-initialization to hook into if you wish to do anything before engines are loaded. After this point, all railtie and engine initializers are ran. *+add_generator_templates+* Finds templates for generators at +lib/templates+ for the application, railities and engines and adds these to the +config.generators.templates+ setting, which will make the templates available for all generators to reference. @@ -552,13 +583,13 @@ TIP: If you have any ordering dependency in your initializers, you can control t *+build_middleware_stack+* Builds the middleware stack for the application, returning an object which has a +call+ method which takes a Rack environment object for the request. -*+eager_load!+* If +config.cache_classes+ is +true+, runs the +config.before_eager_load+ hooks and then calls +eager_load!+ which will load all the Ruby files from +config.eager_load_paths+. +*+eager_load!+* If +config.cache_classes+ is true, runs the +config.before_eager_load+ hooks and then calls +eager_load!+ which will load all the Ruby files from +config.eager_load_paths+. *+finisher_hook+* Provides a hook for after the initialization of process of the application is complete, as well as running all the +config.after_initialize+ blocks for the application, railties and engines. *+set_routes_reloader+* Configures Action Dispatch to reload the routes file using +ActionDispatch::Callbacks.to_prepare+. -*+disable_dependency_loading+* Disables the automatic dependency loading if the +config.cache_classes+ is set to +true+ and +config.dependency_loading+ is set to +false+. +*+disable_dependency_loading+* Disables the automatic dependency loading if the +config.cache_classes+ is set to true and +config.dependency_loading+ is set to false. h3. Changelog diff --git a/railties/guides/source/contribute.textile b/railties/guides/source/contribute.textile index 4bd527d4c7..f9bb80861c 100644 --- a/railties/guides/source/contribute.textile +++ b/railties/guides/source/contribute.textile @@ -14,7 +14,7 @@ h3. How to Contribute? * Sample format : "Active Record Associations":https://github.com/lifo/docrails/blob/3e56a3832415476fdd1cb963980d0ae390ac1ed3/railties/guides/source/association_basics.textile. * Sample output : "Active Record Associations":association_basics.html. * You can build the Guides during testing by running +bundle exec rake generate_guides+ in the +railties+ directory. -* You're encouraged to validate XHTML for the generated guides before commiting your changes by running +bundle exec rake validate_guides+ in the +railties+ directory. +* You're encouraged to validate XHTML for the generated guides before committing your changes by running +bundle exec rake validate_guides+ in the +railties+ directory. * Edge guides "can be consulted online":http://edgeguides.rubyonrails.org/. That website is generated periodically from docrails. h3. What to Contribute? @@ -33,7 +33,7 @@ h3. What to Contribute? h3. How is the process? * The preferred way to contribute is to commit to docrails directly. -* A new guide is only edited by its author until finished though. In that case feedback can be given in its LH ticket. +* A new guide is only edited by its author until finished though. * If you are writing a new guide freely commit to docrails partial work and ping lifo or fxn when done with a first draft. * Guides reviewers will then provide feedback, some of it possibly in form of direct commits to agilize the process. * Eventually the guide will be approved and added to the index. @@ -53,7 +53,7 @@ h3. Rules * If the same guide writer wants to write multiple guides, that's ideally the situation we'd love to be in! However, that guide writer will only receive the cash prize for all the subsequent guides (and not the GitHub or RPM prizes). * Our review team will have the final say on whether the guide is complete and of good enough quality. -All authors should read and follow the "Rails Guides Conventions":https://wiki.github.com/lifo/docrails/rails-guides-conventions and the "Rails API Documentation Conventions":https://wiki.github.com/lifo/docrails/rails-api-documentation-conventions. +All authors should read and follow the "Rails Guides Conventions":ruby_on_rails_guides_guidelines.html and the "Rails API Documentation Conventions":api_documentation_guidelines.html. h3. Translations diff --git a/railties/guides/source/contributing_to_ruby_on_rails.textile b/railties/guides/source/contributing_to_ruby_on_rails.textile index 5eb925d7d2..e6ec061c9a 100644 --- a/railties/guides/source/contributing_to_ruby_on_rails.textile +++ b/railties/guides/source/contributing_to_ruby_on_rails.textile @@ -24,7 +24,7 @@ If you've found a problem in Ruby on Rails which is not a security risk do a sea At the minimum, your issue report needs a title and descriptive text. But that's only a minimum. You should include as much relevant information as possible. You need to at least post the code sample that has the issue. Even better is to include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to replicate the bug and figure out a fix. -Then don't get your hopes up. Unless you have a "Code Red, Mission Critical, The World is Coming to an End" kind of bug, you're creating this issue report in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the issue report will automatically see any activity or that others will jump to fix it. Creating a issue like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with a "I'm having this problem too" comment. +Then don't get your hopes up. Unless you have a "Code Red, Mission Critical, The World is Coming to an End" kind of bug, you're creating this issue report in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the issue report will automatically see any activity or that others will jump to fix it. Creating an issue like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with a "I'm having this problem too" comment. h4. Special Treatment for Security Issues @@ -90,7 +90,7 @@ This command will install all dependencies except the MySQL and PostgreSQL Ruby $ rake test </shell> -You can also run tests for an specific framework, like Action Pack, by going into its directory and executing the same command: +You can also run tests for a specific framework, like Action Pack, by going into its directory and executing the same command: <shell> $ cd actionpack @@ -264,7 +264,7 @@ When working with documentation, please take into account the "API Documentation NOTE: As explained above, ordinary code patches should have proper documentation coverage. docrails is only used for isolated documentation improvements. -WARNING: docrails has a very strict policy: no code can be touched whatsoever, no matter how trivial or small the change. Only RDoc and guides can be edited via docrails. +WARNING: docrails has a very strict policy: no code can be touched whatsoever, no matter how trivial or small the change. Only RDoc and guides can be edited via docrails. Also, CHANGELOGs should never be edited in docrails. If you have an idea for a new guide you can refer to the "contribution page":contribute.html for instructions on getting involved. @@ -309,7 +309,7 @@ Rails follows a simple set of coding style conventions. * a = b and not a=b. * Follow the conventions you see used in the source already. -These are some guidelines and please use your best judgement in using them. +These are some guidelines and please use your best judgment in using them. h4. Sanity Check @@ -350,7 +350,7 @@ Navigate to the Rails "GitHub repository":https://github.com/rails/rails and pre Add the new remote to your local repository on your local machine: <shell> -$ git remote add mine https://<your user name>@github.com/<your user name>/rails.git +$ git remote add mine git@github.com:<your user name>/rails.git </shell> Push to your remote: @@ -361,7 +361,7 @@ $ git push mine my_new_branch h4. Issue a Pull Request -Navigate to the Rails repository you just pushed to (e.g. https://github.com/<your user name>/rails) and press "Pull Request" in the upper right hand corner. +Navigate to the Rails repository you just pushed to (e.g. https://github.com/your-user-name/rails) and press "Pull Request" in the upper right hand corner. Write your branch name in branch field (is filled with master by default) and press "Update Commit Range" diff --git a/railties/guides/source/debugging_rails_applications.textile b/railties/guides/source/debugging_rails_applications.textile index 6f028805d6..f5bee52b5a 100644 --- a/railties/guides/source/debugging_rails_applications.textile +++ b/railties/guides/source/debugging_rails_applications.textile @@ -127,7 +127,7 @@ When something is logged it's printed into the corresponding log if the log leve The available log levels are: +:debug+, +:info+, +:warn+, +:error+, and +:fatal+, corresponding to the log level numbers from 0 up to 4 respectively. To change the default log level, use <ruby> -config.log_level = Logger::WARN # In any environment initializer, or +config.log_level = :warn # In any environment initializer, or Rails.logger.level = 0 # at any time </ruby> @@ -480,11 +480,7 @@ class Author < ActiveRecord::Base def find_recent_comments(limit = 10) debugger - @recent_comments ||= comments.find( - :all, - :conditions => ["created_at > ?", 1.week.ago], - :limit => limit - ) + @recent_comments ||= comments.where("created_at > ?", 1.week.ago).limit(limit) end end </ruby> @@ -493,7 +489,7 @@ TIP: You can use ruby-debug while using +rails console+. Just remember to +requi <shell> $ rails console -Loading development environment (Rails 2.1.0) +Loading development environment (Rails 3.1.0) >> require "ruby-debug" => [] >> author = Author.first @@ -507,15 +503,15 @@ With the code stopped, take a look around: <shell> (rdb:1) list -[6, 15] in /PathTo/project/app/models/author.rb +[2, 9] in /PathTo/project/app/models/author.rb + 2 has_one :editorial + 3 has_many :comments + 4 + 5 def find_recent_comments(limit = 10) 6 debugger - 7 @recent_comments ||= comments.find( - 8 :all, - 9 :conditions => ["created_at > ?", 1.week.ago], - 10 :limit => limit -=> 11 ) - 12 end - 13 end +=> 7 @recent_comments ||= comments.where("created_at > ?", 1.week.ago).limit(limit) + 8 end + 9 end </shell> You are at the end of the line, but... was this line executed? You can inspect the instance variables. diff --git a/railties/guides/source/form_helpers.textile b/railties/guides/source/form_helpers.textile index 4134c9f8ed..9051ede9dd 100644 --- a/railties/guides/source/form_helpers.textile +++ b/railties/guides/source/form_helpers.textile @@ -545,7 +545,7 @@ NOTE: In many cases the built-in date pickers are clumsy as they do not aid the h4. Individual Components -Occasionally you need to display just a single date component such as a year or a month. Rails provides a series of helpers for this, one for each component +select_year+, +select_month+, +select_day+, +select_hour+, +select_minute+, +select_second+. These helpers are fairly straightforward. By default they will generate an input field named after the time component (for example "year" for +select_year+, "month" for +select_month+ etc.) although this can be overriden with the +:field_name+ option. The +:prefix+ option works in the same way that it does for +select_date+ and +select_time+ and has the same default value. +Occasionally you need to display just a single date component such as a year or a month. Rails provides a series of helpers for this, one for each component +select_year+, +select_month+, +select_day+, +select_hour+, +select_minute+, +select_second+. These helpers are fairly straightforward. By default they will generate an input field named after the time component (for example "year" for +select_year+, "month" for +select_month+ etc.) although this can be overridden with the +:field_name+ option. The +:prefix+ option works in the same way that it does for +select_date+ and +select_time+ and has the same default value. The first parameter specifies which value should be selected and can either be an instance of a Date, Time or DateTime, in which case the relevant component will be extracted, or a numerical value. For example @@ -589,7 +589,7 @@ def upload end </ruby> -Once a file has been uploaded, there are a multitude of potential tasks, ranging from where to store the files (on disk, Amazon S3, etc) and associating them with models to resizing image files and generating thumbnails. The intricacies of this are beyond the scope of this guide, but there are several plugins designed to assist with these. Two of the better known ones are "Attachment-Fu":https://github.com/technoweenie/attachment_fu and "Paperclip":http://www.thoughtbot.com/projects/paperclip. +Once a file has been uploaded, there are a multitude of potential tasks, ranging from where to store the files (on disk, Amazon S3, etc) and associating them with models to resizing image files and generating thumbnails. The intricacies of this are beyond the scope of this guide, but there are several libraries designed to assist with these. Two of the better known ones are "CarrierWave":https://github.com/jnicklas/carrierwave and "Paperclip":http://www.thoughtbot.com/projects/paperclip. NOTE: If the user has not selected a file the corresponding parameter will be an empty string. @@ -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/generators.textile b/railties/guides/source/generators.textile index a3181b9ac5..2fa1d6e21d 100644 --- a/railties/guides/source/generators.textile +++ b/railties/guides/source/generators.textile @@ -46,7 +46,7 @@ class InitializerGenerator < Rails::Generators::Base end </ruby> -NOTE: +create_file+ is a method provided by +Thor::Actions+ and the documentation for it and its brethren can be found at "rdoc.info":http://rdoc.info/github/wycats/thor/master/Thor/Actions. +NOTE: +create_file+ is a method provided by +Thor::Actions+. Documentation for +create_file+ and other Thor methods can be found in "Thor's documentation":http://rdoc.info/github/wycats/thor/master/Thor/Actions.html Our new generator is quite simple: it inherits from +Rails::Generators::Base+ and has one method definition. Each public method in the generator is executed when a generator is invoked. Finally, we invoke the +create_file+ method that will create a file at the given destination with the given content. If you are familiar with the Rails Application Templates API, you'll feel right at home with the new generators API. @@ -131,7 +131,7 @@ And let's execute our generator: $ rails generate initializer core_extensions </shell> -We can see that now a initializer named core_extensions was created at +config/initializers/core_extensions.rb+ with the contents of our template. That means that +copy_file+ copied a file in our source root to the destination path we gave. The method +file_name+ is automatically created when we inherit from +Rails::Generators::NamedBase+. +We can see that now an initializer named core_extensions was created at +config/initializers/core_extensions.rb+ with the contents of our template. That means that +copy_file+ copied a file in our source root to the destination path we gave. The method +file_name+ is automatically created when we inherit from +Rails::Generators::NamedBase+. The methods that are available for generators are covered in the "final section":#generator-methods of this guide. @@ -365,7 +365,7 @@ $ rails generate scaffold Comment body:text Fallbacks allow your generators to have a single responsibility, increasing code reuse and reducing the amount of duplication. -h3. Application templates +h3. Application Templates Now that you've seen how generators can be used _inside_ an application, did you know they can also be used to _generate_ applications too? This kind of generator is referred as a "template". diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index 8a9086f416..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. @@ -503,7 +538,7 @@ def index end </ruby> -+Post.all+ calls the +Post+ model to return all of the posts currently in the database. The result of this call is an array of posts that we store in a instance variable called +@posts+. ++Post.all+ calls the +Post+ model to return all of the posts currently in the database. The result of this call is an array of posts that we store in an instance variable called +@posts+. TIP: For more information on finding records with Active Record, see "Active Record Query Interface":active_record_querying.html. diff --git a/railties/guides/source/i18n.textile b/railties/guides/source/i18n.textile index 608643b3d3..0c8e4e974d 100644 --- a/railties/guides/source/i18n.textile +++ b/railties/guides/source/i18n.textile @@ -809,7 +809,7 @@ That does not mean you're stuck with these limitations, though. The Ruby I18n ge I18n.backend = Globalize::Backend::Static.new </ruby> -You can also use the Chain backend to chain multiple backends together. This is useful when you want to use standard translations with a Simple backend but store custom application translations in a database or other backends. For example, you could use the ActiveRecord backend and fall back to the (default) Simple backend: +You can also use the Chain backend to chain multiple backends together. This is useful when you want to use standard translations with a Simple backend but store custom application translations in a database or other backends. For example, you could use the Active Record backend and fall back to the (default) Simple backend: <ruby> I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n.backend) diff --git a/railties/guides/source/initialization.textile b/railties/guides/source/initialization.textile index 638830cd83..340699419b 100644 --- a/railties/guides/source/initialization.textile +++ b/railties/guides/source/initialization.textile @@ -20,15 +20,15 @@ h4. +bin/rails+ The actual +rails+ command is kept in _bin/rails_ at the and goes like this: <ruby> - #!/usr/bin/env ruby - - begin - require "rails/cli" - rescue LoadError - railties_path = File.expand_path('../../railties/lib', __FILE__) - $:.unshift(railties_path) - require "rails/cli" - end +#!/usr/bin/env ruby + +begin + require "rails/cli" +rescue LoadError + railties_path = File.expand_path('../../railties/lib', __FILE__) + $:.unshift(railties_path) + require "rails/cli" +end </ruby> This file will attempt to load +rails/cli+ and if it cannot find it then add the +railties/lib+ path to the load path (+$:+) and will then try to require it again. @@ -38,22 +38,22 @@ h4. +railites/lib/rails/cli.rb+ This file looks like this: <ruby> - require 'rbconfig' - require 'rails/script_rails_loader' +require 'rbconfig' +require 'rails/script_rails_loader' - # If we are inside a Rails application this method performs an exec and thus - # the rest of this script is not run. - Rails::ScriptRailsLoader.exec_script_rails! +# If we are inside a Rails application this method performs an exec and thus +# the rest of this script is not run. +Rails::ScriptRailsLoader.exec_script_rails! - require 'rails/ruby_version_check' - Signal.trap("INT") { puts; exit } +require 'rails/ruby_version_check' +Signal.trap("INT") { puts; exit } - if ARGV.first == 'plugin' - ARGV.shift - require 'rails/commands/plugin_new' - else - require 'rails/commands/application' - end +if ARGV.first == 'plugin' + ARGV.shift + require 'rails/commands/plugin_new' +else + require 'rails/commands/application' +end </ruby> The +rbconfig+ file here is out of Ruby's standard library and provides us with the +RbConfig+ class which contains useful information dependent on how Ruby was compiled. We'll see this in use in +railties/lib/rails/script_rails_loader+. @@ -76,46 +76,46 @@ The +rails/script_rails_loader+ file uses +RbConfig::Config+ to gather up the +b Back in +rails/cli+, the next line is this: <ruby> - Rails::ScriptRailsLoader.exec_script_rails! +Rails::ScriptRailsLoader.exec_script_rails! </ruby> This method is defined in +rails/script_rails_loader+ like this: <ruby> - def self.exec_script_rails! - cwd = Dir.pwd - return unless in_rails_application? || in_rails_application_subdirectory? - exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application? - Dir.chdir("..") do - # Recurse in a chdir block: if the search fails we want to be sure - # the application is generated in the original working directory. - exec_script_rails! unless cwd == Dir.pwd - end - rescue SystemCallError - # could not chdir, no problem just return +def self.exec_script_rails! + cwd = Dir.pwd + return unless in_rails_application? || in_rails_application_subdirectory? + exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application? + Dir.chdir("..") do + # Recurse in a chdir block: if the search fails we want to be sure + # the application is generated in the original working directory. + exec_script_rails! unless cwd == Dir.pwd end +rescue SystemCallError + # could not chdir, no problem just return +end </ruby> This method will first check if the current working directory (+cwd+) is a Rails application or is a subdirectory of one. The way to determine this is defined in the +in_rails_application?+ method like this: <ruby> - def self.in_rails_application? - File.exists?(SCRIPT_RAILS) - end +def self.in_rails_application? + File.exists?(SCRIPT_RAILS) +end </ruby> The +SCRIPT_RAILS+ constant defined earlier is used here, with +File.exists?+ checking for its presence in the current directory. If this method returns +false+, then +in_rails_application_subdirectory?+ will be used: <ruby> - def self.in_rails_application_subdirectory?(path = Pathname.new(Dir.pwd)) - File.exists?(File.join(path, SCRIPT_RAILS)) || !path.root? && in_rails_application_subdirectory?(path.parent) - end +def self.in_rails_application_subdirectory?(path = Pathname.new(Dir.pwd)) + File.exists?(File.join(path, SCRIPT_RAILS)) || !path.root? && in_rails_application_subdirectory?(path.parent) +end </ruby> This climbs the directory tree until it reaches a path which contains a +script/rails+ file. If a directory is reached which contains this file then this line will run: <ruby> - exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application? +exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application? </ruby> This is effectively the same as doing +ruby script/rails [arguments]+. Where +[arguments]+ at this point in time is simply "server". @@ -125,9 +125,9 @@ h4. +script/rails+ This file looks like this: <ruby> - APP_PATH = File.expand_path('../../config/application', __FILE__) - require File.expand_path('../../config/boot', __FILE__) - require 'rails/commands' +APP_PATH = File.expand_path('../../config/application', __FILE__) +require File.expand_path('../../config/boot', __FILE__) +require 'rails/commands' </ruby> The +APP_PATH+ constant here will be used later in +rails/commands+. The +config/boot+ file that +script/rails+ references is the +config/boot.rb+ file in our application which is responsible for loading Bundler and setting it up. @@ -137,19 +137,19 @@ h4. +config/boot.rb+ +config/boot.rb+ contains this: <ruby> - require 'rubygems' +require 'rubygems' - # Set up gems listed in the Gemfile. - gemfile = File.expand_path('../../Gemfile', __FILE__) - begin - ENV['BUNDLE_GEMFILE'] = gemfile - require 'bundler' - Bundler.setup - rescue Bundler::GemNotFound => e - STDERR.puts e.message - STDERR.puts "Try running `bundle install`." - exit! - end if File.exist?(gemfile) +# Set up gems listed in the Gemfile. +gemfile = File.expand_path('../../Gemfile', __FILE__) +begin + ENV['BUNDLE_GEMFILE'] = gemfile + require 'bundler' + Bundler.setup +rescue Bundler::GemNotFound => e + STDERR.puts e.message + STDERR.puts "Try running `bundle install`." + exit! +end if File.exist?(gemfile) </ruby> In a standard Rails application, there's a +Gemfile+ which declares all dependencies of the application. +config/boot.rb+ sets +ENV["BUNDLE_GEMFILE"]+ to the location of this file, then requires Bundler and calls +Bundler.setup+ which adds the dependencies of the application (including all the Rails parts) to the load path, making them available for the application to load. The gems that a Rails 3.1 application depends on are as follows: @@ -186,34 +186,34 @@ h4. +rails/commands.rb+ Once +config/boot.rb+ has finished, the next file that is required is +rails/commands+ which will execute a command based on the arguments passed in. In this case, the +ARGV+ array simply contains +server+ which is extracted into the +command+ variable using these lines: <ruby> - aliases = { - "g" => "generate", - "c" => "console", - "s" => "server", - "db" => "dbconsole" - } +aliases = { + "g" => "generate", + "c" => "console", + "s" => "server", + "db" => "dbconsole" +} - command = ARGV.shift - command = aliases[command] || command +command = ARGV.shift +command = aliases[command] || command </ruby> If we used <tt>s</tt> rather than +server+, Rails will use the +aliases+ defined in the file and match them to their respective commands. With the +server+ command, Rails will run this code: <ruby> - when 'server' - # Change to the application's path if there is no config.ru file in current dir. - # This allows us to run script/rails server from other directories, but still get - # the main config.ru and properly set the tmp directory. - Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exists?(File.expand_path("config.ru")) - - require 'rails/commands/server' - Rails::Server.new.tap { |server| - # We need to require application after the server sets environment, - # otherwise the --environment option given to the server won't propagate. - require APP_PATH - Dir.chdir(Rails.application.root) - server.start - } +when 'server' + # Change to the application's path if there is no config.ru file in current dir. + # This allows us to run script/rails server from other directories, but still get + # the main config.ru and properly set the tmp directory. + Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exists?(File.expand_path("config.ru")) + + require 'rails/commands/server' + Rails::Server.new.tap { |server| + # We need to require application after the server sets environment, + # otherwise the --environment option given to the server won't propagate. + require APP_PATH + Dir.chdir(Rails.application.root) + server.start + } </ruby> This file will change into the root of the directory (a path two directories back from +APP_PATH+ which points at +config/application.rb+), but only if the +config.ru+ file isn't found. This then requires +rails/commands/server+ which requires +action_dispatch+ and sets up the +Rails::Server+ class. @@ -239,7 +239,7 @@ The +methods.rb+ file is responsible for defining methods such as +camelize+, +u In this file there are a lot of lines such as this inside the +ActiveSupport+ module: <ruby> - autoload :Inflector +autoload :Inflector </ruby> Due to the overriding of the +autoload+ method, Ruby will know to look for this file at +activesupport/lib/active_support/inflector.rb+ when the +Inflector+ class is first referenced. @@ -263,10 +263,10 @@ h4. +rails/commands/server.rb+ The +Rails::Server+ class is defined in this file as inheriting from +Rack::Server+. When +Rails::Server.new+ is called, this calls the +initialize+ method in +rails/commands/server.rb+: <ruby> - def initialize(*) - super - set_environment - end +def initialize(*) + super + set_environment +end </ruby> Firstly, +super+ is called which calls the +initialize+ method on +Rack::Server+. @@ -278,10 +278,10 @@ h4. Rack: +lib/rack/server.rb+ The +initialize+ method in +Rack::Server+ simply sets a couple of variables: <ruby> - def initialize(options = nil) - @options = options - @app = options[:app] if options && options[:app] - end +def initialize(options = nil) + @options = options + @app = options[:app] if options && options[:app] +end </ruby> In this case, +options+ will be +nil+ so nothing happens in this method. @@ -289,64 +289,64 @@ In this case, +options+ will be +nil+ so nothing happens in this method. After +super+ has finished in +Rack::Server+, we jump back to +rails/commands/server.rb+. At this point, +set_environment+ is called within the context of the +Rails::Server+ object and this method doesn't appear to do much at first glance: <ruby> - def set_environment - ENV["RAILS_ENV"] ||= options[:environment] - end +def set_environment + ENV["RAILS_ENV"] ||= options[:environment] +end </ruby> In fact, the +options+ method here does quite a lot. This method is defined in +Rack::Server+ like this: <ruby> - def options - @options ||= parse_options(ARGV) - end +def options + @options ||= parse_options(ARGV) +end </ruby> Then +parse_options+ is defined like this: <ruby> - def parse_options(args) - options = default_options +def parse_options(args) + options = default_options - # Don't evaluate CGI ISINDEX parameters. - # http://hoohoo.ncsa.uiuc.edu/cgi/cl.html - args.clear if ENV.include?("REQUEST_METHOD") + # Don't evaluate CGI ISINDEX parameters. + # http://hoohoo.ncsa.uiuc.edu/cgi/cl.html + args.clear if ENV.include?("REQUEST_METHOD") - options.merge! opt_parser.parse! args - options[:config] = ::File.expand_path(options[:config]) - ENV["RACK_ENV"] = options[:environment] - options - end + options.merge! opt_parser.parse! args + options[:config] = ::File.expand_path(options[:config]) + ENV["RACK_ENV"] = options[:environment] + options +end </ruby> With the +default_options+ set to this: <ruby> - def default_options - { - :environment => ENV['RACK_ENV'] || "development", - :pid => nil, - :Port => 9292, - :Host => "0.0.0.0", - :AccessLog => [], - :config => "config.ru" - } - end +def default_options + { + :environment => ENV['RACK_ENV'] || "development", + :pid => nil, + :Port => 9292, + :Host => "0.0.0.0", + :AccessLog => [], + :config => "config.ru" + } +end </ruby> There is no +REQUEST_METHOD+ key in +ENV+ so we can skip over that line. The next line merges in the options from +opt_parser+ which is defined plainly in +Rack::Server+ <ruby> - def opt_parser - Options.new - end +def opt_parser + Options.new +end </ruby> The class *is* defined in +Rack::Server+, but is overwritten in +Rails::Server+ to take different arguments. Its +parse!+ method begins like this: <ruby> - def parse!(args) - args, options = args.dup, {} +def parse!(args) + args, options = args.dup, {} opt_parser = OptionParser.new do |opts| opts.banner = "Usage: rails server [mongrel, thin, etc] [options]" @@ -362,100 +362,101 @@ h4. +Rails::Server#start+ This method is defined like this: <ruby> - def start - puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}" - puts "=> Rails #{Rails.version} application starting in #{Rails.env} on http://#{options[:Host]}:#{options[:Port]}" - puts "=> Call with -d to detach" unless options[:daemonize] - trap(:INT) { exit } - puts "=> Ctrl-C to shutdown server" unless options[:daemonize] - - #Create required tmp directories if not found - %w(cache pids sessions sockets).each do |dir_to_make| - FileUtils.mkdir_p(Rails.root.join('tmp', dir_to_make)) - end - - super - ensure - # The '-h' option calls exit before @options is set. - # If we call 'options' with it unset, we get double help banners. - puts 'Exiting' unless @options && options[:daemonize] +def start + puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}" + puts "=> Rails #{Rails.version} application starting in #{Rails.env} on http://#{options[:Host]}:#{options[:Port]}" + puts "=> Call with -d to detach" unless options[:daemonize] + trap(:INT) { exit } + puts "=> Ctrl-C to shutdown server" unless options[:daemonize] + + #Create required tmp directories if not found + %w(cache pids sessions sockets).each do |dir_to_make| + FileUtils.mkdir_p(Rails.root.join('tmp', dir_to_make)) end + + super +ensure + # The '-h' option calls exit before @options is set. + # If we call 'options' with it unset, we get double help banners. + puts 'Exiting' unless @options && options[:daemonize] +end </ruby> This is where the first output of the Rails initialization happens. This method creates a trap for +INT+ signals, so if you +CTRL+C+ the server, it will exit the process. As we can see from the code here, it will create the +tmp/cache+, +tmp/pids+, +tmp/sessions+ and +tmp/sockets+ directories if they don't already exist prior to calling +super+. The +super+ method will call +Rack::Server.start+ which begins its definition like this: <ruby> - def start - if options[:warn] - $-w = true - end +def start + if options[:warn] + $-w = true + end - if includes = options[:include] - $LOAD_PATH.unshift(*includes) - end + if includes = options[:include] + $LOAD_PATH.unshift(*includes) + end - if library = options[:require] - require library - end + if library = options[:require] + require library + end - if options[:debug] - $DEBUG = true - require 'pp' - p options[:server] - pp wrapped_app - pp app - end + if options[:debug] + $DEBUG = true + require 'pp' + p options[:server] + pp wrapped_app + pp app + end +end </ruby> In a Rails application, these options are not set at all and therefore aren't used at all. The first line of code that's executed in this method is a call to this method: <ruby> - wrapped_app +wrapped_app </ruby> This method calls another method: <ruby> - @wrapped_app ||= build_app app +@wrapped_app ||= build_app app </ruby> Then the +app+ method here is defined like so: <ruby> - def app - @app ||= begin - if !::File.exist? options[:config] - abort "configuration #{options[:config]} not found" - end - - app, options = Rack::Builder.parse_file(self.options[:config], opt_parser) - self.options.merge! options - app +def app + @app ||= begin + if !::File.exist? options[:config] + abort "configuration #{options[:config]} not found" end + + app, options = Rack::Builder.parse_file(self.options[:config], opt_parser) + self.options.merge! options + app end +end </ruby> The +options[:config]+ value defaults to +config.ru+ which contains this: <ruby> - # This file is used by Rack-based servers to start the application. +# This file is used by Rack-based servers to start the application. - require ::File.expand_path('../config/environment', __FILE__) - run YourApp::Application +require ::File.expand_path('../config/environment', __FILE__) +run YourApp::Application </ruby> The +Rack::Builder.parse_file+ method here takes the content from this +config.ru+ file and parses it using this code: <ruby> - app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app", +app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app", TOPLEVEL_BINDING, config </ruby> The <ruby>initialize</ruby> method will take the block here and execute it within an instance of +Rack::Builder+. This is where the majority of the initialization process of Rails happens. The chain of events that this simple line sets off will be the focus of a large majority of this guide. The +require+ line for +config/environment.rb+ in +config.ru+ is the first to run: <ruby> - require ::File.expand_path('../config/environment', __FILE__) +require ::File.expand_path('../config/environment', __FILE__) </ruby> h4. +config/environment.rb+ @@ -475,7 +476,7 @@ h3. Loading Rails The next line in +config/application.rb+ is: <ruby> - require 'rails/all' +require 'rails/all' </ruby> h4. +railties/lib/rails/all.rb+ @@ -483,20 +484,20 @@ h4. +railties/lib/rails/all.rb+ This file is responsible for requiring all the individual parts of Rails like so: <ruby> - require "rails" +require "rails" - %w( +%w( active_record action_controller action_mailer active_resource rails/test_unit - ).each do |framework| - begin - require "#{framework}/railtie" - rescue LoadError - end +).each do |framework| + begin + require "#{framework}/railtie" + rescue LoadError end +end </ruby> First off the line is the +rails+ require itself. @@ -518,9 +519,9 @@ h4. +active_support/core_ext/kernel/reporting.rb+ This is the first of the many Active Support core extensions that come with Rails. This one in particular defines methods in the +Kernel+ module which is mixed in to the +Object+ class so the methods are available on +main+ and can therefore be called like this: <ruby> - silence_warnings do - # some code - end +silence_warnings do + # some code +end </ruby> These methods can be used to silence STDERR responses and the +silence_stream+ allows you to also silence other streams. Additionally, this mixin allows you to suppress exceptions and capture streams. For more information see the "Silencing Warnings, Streams, and Exceptions":http://guides.rubyonrails.org/active_support_core_extensions.html#silencing-warnings-streams-and-exceptions section from the Active Support Core Extensions Guide. @@ -635,14 +636,14 @@ h4. +railties/lib/rails/rack.rb+ The final file to be loaded by +railties/lib/rails/configuration.rb+ is +rails/rack+ which defines some simple autoloads: <ruby> - module Rails - module Rack - autoload :Debugger, "rails/rack/debugger" - autoload :Logger, "rails/rack/logger" - autoload :LogTailer, "rails/rack/log_tailer" - autoload :Static, "rails/rack/static" - end +module Rails + module Rack + autoload :Debugger, "rails/rack/debugger" + autoload :Logger, "rails/rack/logger" + autoload :LogTailer, "rails/rack/log_tailer" + autoload :Static, "rails/rack/static" end +end </ruby> Once this file is finished loading, then the +Rails::Configuration+ class is initialized. This completes the loading of +railties/lib/rails/configuration.rb+ and now we jump back to the loading of +railties/lib/rails/railtie.rb+, where the next file loaded is +active_support/inflector+. @@ -652,17 +653,17 @@ h4. +activesupport/lib/active_support/inflector.rb+ +active_support/inflector.rb+ requires a series of file which are responsible for setting up the basics for knowing how to pluralize and singularize words. These files are: <ruby> - require 'active_support/inflector/inflections' - require 'active_support/inflector/transliterate' - require 'active_support/inflector/methods' +require 'active_support/inflector/inflections' +require 'active_support/inflector/transliterate' +require 'active_support/inflector/methods' - require 'active_support/inflections' - require 'active_support/core_ext/string/inflections' +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". @@ -721,22 +722,22 @@ h4. +activesupport/lib/active_support/i18n_railtie.rb+ This file is the first file that sets up configuration with these lines inside the class: <ruby> - class Railtie < Rails::Railtie - config.i18n = ActiveSupport::OrderedOptions.new - config.i18n.railties_load_path = [] - config.i18n.load_path = [] - config.i18n.fallbacks = ActiveSupport::OrderedOptions.new +class Railtie < Rails::Railtie + config.i18n = ActiveSupport::OrderedOptions.new + config.i18n.railties_load_path = [] + config.i18n.load_path = [] + config.i18n.fallbacks = ActiveSupport::OrderedOptions.new </ruby> By inheriting from +Rails::Railtie+ the +Rails::Railtie#inherited+ method is called: <ruby> - def inherited(base) - unless base.abstract_railtie? - base.send(:include, Railtie::Configurable) - subclasses << base - end +def inherited(base) + unless base.abstract_railtie? + base.send(:include, Railtie::Configurable) + subclasses << base end +end </ruby> This first checks if the Railtie that's inheriting it is a component of Rails itself: @@ -763,15 +764,15 @@ end The +config+ method used at the top of +I18n::Railtie+ is defined on +Rails::Railtie+ and is defined like this: <ruby> - def config - @config ||= Railtie::Configuration.new - end +def config + @config ||= Railtie::Configuration.new +end </ruby> At this point, that +Railtie::Configuration+ constant is automatically loaded which causes the +rails/railties/configuration+ file to be loaded. The line for this is this particular line in +railties/lib/rails/railtie.rb+: <ruby> - autoload :Configuration, "rails/railtie/configuration" +autoload :Configuration, "rails/railtie/configuration" </ruby> h4. +railties/lib/rails/railtie/configuration.rb+ @@ -781,15 +782,15 @@ This file begins with a require out to +rails/configuration+ which has already b This file defines the +Rails::Railtie::Configuration+ class which is responsible for providing a way to easily configure railties and it's the +initialize+ method here which is called by the +config+ method back in the +i18n_railtie.rb+ file. The methods on this object don't exist, and so are rescued by the +method_missing+ defined further down in +configuration.rb+: <ruby> - def method_missing(name, *args, &blk) - if name.to_s =~ /=$/ - @@options[$`.to_sym] = args.first - elsif @@options.key?(name) - @@options[name] - else - super - end +def method_missing(name, *args, &blk) + if name.to_s =~ /=$/ + @@options[$`.to_sym] = args.first + elsif @@options.key?(name) + @@options[name] + else + super end +end </ruby> So therefore when an option is referred to it simply stores the value as the key if it's used in a setter context, or retrieves it if used in a getter context. Nothing fancy going on there. @@ -799,21 +800,21 @@ h4. Back to +activesupport/lib/active_support/i18n_railtie.rb+ After the configuration method the +reloader+ method is defined, and then the first of of Railties' initializers is defined: +i18n.callbacks+. <ruby> - initializer "i18n.callbacks" do - ActionDispatch::Reloader.to_prepare do - I18n::Railtie.reloader.execute_if_updated - end +initializer "i18n.callbacks" do + ActionDispatch::Reloader.to_prepare do + I18n::Railtie.reloader.execute_if_updated end +end </ruby> The +initializer+ method (from the +Rails::Initializable+ module) here doesn't run the block, but rather stores it to be run later on: <ruby> - def initializer(name, opts = {}, &blk) - raise ArgumentError, "A block must be passed when defining an initializer" unless blk - opts[:after] ||= initializers.last.name unless initializers.empty? || initializers.find { |i| i.name == opts[:before] } - initializers << Initializer.new(name, nil, opts, &blk) - end +def initializer(name, opts = {}, &blk) + raise ArgumentError, "A block must be passed when defining an initializer" unless blk + opts[:after] ||= initializers.last.name unless initializers.empty? || initializers.find { |i| i.name == opts[:before] } + initializers << Initializer.new(name, nil, opts, &blk) +end </ruby> An initializer can be configured to run before or after another initializer, which we'll see a couple of times throughout this initialization process. Anything that inherits from +Rails::Railtie+ may also make use of the +initializer+ method, something which is covered in the "Configuration guide":[http://ryanbigg.com/guides/configuring.html#rails-railtie-initializer]. @@ -821,25 +822,25 @@ An initializer can be configured to run before or after another initializer, whi The +Initializer+ class here is defined within the +Rails::Initializable+ module and its +initialize+ method is defined to just set up a couple of variables: <ruby> - def initialize(name, context, options, &block) - @name, @context, @options, @block = name, context, options, block - end +def initialize(name, context, options, &block) + @name, @context, @options, @block = name, context, options, block +end </ruby> Once this +initialize+ method is finished, the object is added to the object the +initializers+ method returns: <ruby> - def initializers - @initializers ||= self.class.initializers_for(self) - end +def initializers + @initializers ||= self.class.initializers_for(self) +end </ruby> If +@initializers+ isn't set (which it won't be at this point), the +intializers_for+ method will be called for this class. <ruby> - def initializers_for(binding) - Collection.new(initializers_chain.map { |i| i.bind(binding) }) - end +def initializers_for(binding) + Collection.new(initializers_chain.map { |i| i.bind(binding) }) +end </ruby> The +Collection+ class in +railties/lib/rails/initializable.rb+ inherits from +Array+ and includes the +TSort+ module which is used to sort out the order of the initializers based on the order they are placed in. @@ -847,57 +848,57 @@ The +Collection+ class in +railties/lib/rails/initializable.rb+ inherits from +A The +initializers_chain+ method referenced in the +initializers_for+ method is defined like this: <rub> - def initializers_chain - initializers = Collection.new - ancestors.reverse_each do | klass | - next unless klass.respond_to?(:initializers) - initializers = initializers + klass.initializers - end - initializers +def initializers_chain + initializers = Collection.new + ancestors.reverse_each do | klass | + next unless klass.respond_to?(:initializers) + initializers = initializers + klass.initializers end + initializers +end </ruby> This method collects the initializers from the ancestors of this class and adds them to a new +Collection+ object using the <tt>+</tt> method which is defined like this for the <tt>Collection</tt> class: <ruby> - def +(other) - Collection.new(to_a + other.to_a) - end +def +(other) + Collection.new(to_a + other.to_a) +end </ruby> -So this <tt>+</tt> method is overriden to return a new collection comprising of the existing collection as an array and then using the <tt>Array#+</tt> method combines these two collections, returning a "super" +Collection+ object. In this case, the only initializer that's going to be in this new +Collection+ object is the +i18n.callbacks+ initializer. +So this <tt>+</tt> method is overridden to return a new collection comprising of the existing collection as an array and then using the <tt>Array#+</tt> method combines these two collections, returning a "super" +Collection+ object. In this case, the only initializer that's going to be in this new +Collection+ object is the +i18n.callbacks+ initializer. The next method to be called after this +initializer+ method is the +after_initialize+ method on the +config+ object, which is defined like this: <ruby> - def after_initialize(&block) - ActiveSupport.on_load(:after_initialize, :yield => true, &block) - end +def after_initialize(&block) + ActiveSupport.on_load(:after_initialize, :yield => true, &block) +end </ruby> The +on_load+ method here is provided by the +active_support/lazy_load_hooks+ file which was required earlier and is defined like this: <ruby> - def self.on_load(name, options = {}, &block) - if base = @loaded[name] - execute_hook(base, options, block) - else - @load_hooks[name] << [block, options] - end +def self.on_load(name, options = {}, &block) + if base = @loaded[name] + execute_hook(base, options, block) + else + @load_hooks[name] << [block, options] end +end </ruby> The +@loaded+ variable here is a hash containing elements representing the different components of Rails that have been loaded at this stage. Currently, this hash is empty. So the +else+ is executed here, using the +@load_hooks+ variable defined in +active_support/lazy_load_hooks+: <ruby> - @load_hooks = Hash.new {|h,k| h[k] = [] } +@load_hooks = Hash.new {|h,k| h[k] = [] } </ruby> This defines a new hash which has keys that default to empty arrays. This saves Rails from having to do something like this instead: <ruby> - @load_hooks[name] = [] - @load_hooks[name] << [block, options] +@load_hooks[name] = [] +@load_hooks[name] << [block, options] </ruby> The value added to this array here consists of the block and options passed to +after_initialize+. @@ -929,11 +930,11 @@ h4. +activesupport/lib/action_dispatch.rb+ This file attempts to locate the +active_support+ and +active_model+ libraries by looking a couple of directories back from the current file and then adds the +active_support+ and +active_model+ +lib+ directories to the load path, but only if they aren't already, which they are. <ruby> - activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__) - $:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) +activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__) +$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) - activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__) - $:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path) +activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__) +$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path) </ruby> In effect, these lines only define the +activesupport_path+ and +activemodel_path+ variables and nothing more. @@ -941,23 +942,23 @@ In effect, these lines only define the +activesupport_path+ and +activemodel_pat The next two requires in this file are already done, so they are not run: <ruby> - require 'active_support' - require 'active_support/dependencies/autoload' +require 'active_support' +require 'active_support/dependencies/autoload' </ruby> The following require is to +action_pack+ (+activesupport/lib/action_pack.rb+) which has a 22-line copyright notice at the top of it and ends in a simple require to +action_pack/version+. This file, like other +version.rb+ files before it, defines the +ActionPack::VERSION+ constant: <ruby> - module ActionPack - module VERSION #:nodoc: - MAJOR = 3 - MINOR = 1 - TINY = 0 - PRE = "beta" - - STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') - end +module ActionPack + module VERSION #:nodoc: + MAJOR = 3 + MINOR = 1 + TINY = 0 + PRE = "beta" + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end +end </ruby> Once +action_pack+ is finished, then +active_model+ is required. @@ -967,16 +968,16 @@ h4. +activemodel/lib/active_model.rb+ This file makes a require to +active_model/version+ which defines the version for Active Model: <ruby> - module ActiveModel - module VERSION #:nodoc: - MAJOR = 3 - MINOR = 1 - TINY = 0 - PRE = "beta" - - STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') - end +module ActiveModel + module VERSION #:nodoc: + MAJOR = 3 + MINOR = 1 + TINY = 0 + PRE = "beta" + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end +end </ruby> Once the +version.rb+ file is loaded, the +ActiveModel+ module has its autoloaded constants defined as well as a sub-module called +ActiveModel::Serializers+ which has autoloads of its own. When the +ActiveModel+ module is closed the +active_support/i18n+ file is required. @@ -986,15 +987,15 @@ h4. +activesupport/lib/active_support/i18n.rb+ This is where the +i18n+ gem is required and first configured: <ruby> - begin - require 'i18n' - require 'active_support/lazy_load_hooks' - rescue LoadError => e - $stderr.puts "You don't have i18n installed in your application. Please add it to your Gemfile and run bundle install" - raise e - end +begin + require 'i18n' + require 'active_support/lazy_load_hooks' +rescue LoadError => e + $stderr.puts "You don't have i18n installed in your application. Please add it to your Gemfile and run bundle install" + raise e +end - I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml" +I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml" </ruby> In effect, the +I18n+ module first defined by +i18n_railtie+ is extended by the +i18n+ gem, rather than the other way around. This has no ill effect. They both work on the same way. @@ -1012,9 +1013,9 @@ h4. Back to +activesupport/lib/action_dispatch.rb+ The remainder of this file requires the +rack+ file from the Rack gem which defines the +Rack+ module. After +rack+, there's autoloads defined for the +Rack+, +ActionDispatch+, +ActionDispatch::Http+, +ActionDispatch::Session+. A new method called +autoload_under+ is used here, and this simply prefixes the files where the modules are autoloaded from with the path specified. For example here: <ruby> - autoload_under 'testing' do - autoload :Assertions - ... +autoload_under 'testing' do + autoload :Assertions +... </ruby> The +Assertions+ module is in the +action_dispatch/testing+ folder rather than simply +action_dispatch+. @@ -1046,25 +1047,25 @@ This file begins by detecting if the +lib+ directories of +active_support+ and + The first three requires have already been done by other files and so aren't loaded here, but the 4th require, the one to +arel+ will require the file provided by the Arel gem, which defines the +Arel+ module. <ruby> - require 'active_support' - require 'active_support/i18n' - require 'active_model' - require 'arel' +require 'active_support' +require 'active_support/i18n' +require 'active_model' +require 'arel' </ruby> The 5th require in this file is one to +active_record/version+ which defines the +ActiveRecord::VERSION+ constant: <ruby> - module ActiveRecord - module VERSION #:nodoc: - MAJOR = 3 - MINOR = 1 - TINY = 0 - PRE = "beta" - - STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') - end +module ActiveRecord + module VERSION #:nodoc: + MAJOR = 3 + MINOR = 1 + TINY = 0 + PRE = "beta" + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end +end </ruby> Once these requires are finished, the base for the +ActiveRecord+ module is defined along with its autoloads. @@ -1072,9 +1073,9 @@ Once these requires are finished, the base for the +ActiveRecord+ module is defi Near the end of the file, we see this line: <ruby> - ActiveSupport.on_load(:active_record) do - Arel::Table.engine = self - end +ActiveSupport.on_load(:active_record) do + Arel::Table.engine = self +end </ruby> This will set the engine for +Arel::Table+ to be +ActiveRecord::Base+. @@ -1082,7 +1083,7 @@ This will set the engine for +Arel::Table+ to be +ActiveRecord::Base+. The file then finishes with this line: <ruby> - I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml' +I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml' </ruby> This will add the translations from +activerecord/lib/active_record/locale/en.yml+ to the load path for +I18n+, with this file being parsed when all the translations are loaded. @@ -1092,8 +1093,8 @@ h4. Back to +activerecord/lib/active_record/railtie.rb+ The next two <tt>require</tt>s in this file aren't run because their files are already required, with +rails+ being required by +rails/all+ and +active_model/railtie+ being required from +action_dispatch+. <ruby> - require "rails" - require "active_model/railtie" +require "rails" +require "active_model/railtie" </ruby> The next +require+ in this file is to +action_controller/railtie+. @@ -1103,9 +1104,9 @@ h4. +actionpack/lib/action_controller/railtie.rb+ This file begins with a couple more requires to files that have already been loaded: <ruby> - require "rails" - require "action_controller" - require "action_dispatch/railtie" +require "rails" +require "action_controller" +require "action_dispatch/railtie" </ruby> However the require after these is to a file that hasn't yet been loaded, +action_view/railtie+, which begins by requiring +action_view+. diff --git a/railties/guides/source/layouts_and_rendering.textile b/railties/guides/source/layouts_and_rendering.textile index d67668df91..ba45b84242 100644 --- a/railties/guides/source/layouts_and_rendering.textile +++ b/railties/guides/source/layouts_and_rendering.textile @@ -123,7 +123,7 @@ Cache-Control: no-cache $ </shell> -We see there is an empty response (no data after the +Cache-Control+ line), but the request was successful because Rails has set the response to 200 OK. You can set the +:status+ option on render to change this response. Rendering nothing can be useful for AJAX requests where all you want to send back to the browser is an acknowledgement that the request was completed. +We see there is an empty response (no data after the +Cache-Control+ line), but the request was successful because Rails has set the response to 200 OK. You can set the +:status+ option on render to change this response. Rendering nothing can be useful for AJAX requests where all you want to send back to the browser is an acknowledgment that the request was completed. TIP: You should probably be using the +head+ method, discussed later in this guide, instead of +render :nothing+. This provides additional flexibility and makes it explicit that you're only generating HTTP headers. diff --git a/railties/guides/source/migrations.textile b/railties/guides/source/migrations.textile index d60b68ec7f..dbbf8f3b51 100644 --- a/railties/guides/source/migrations.textile +++ b/railties/guides/source/migrations.textile @@ -55,6 +55,8 @@ class AddReceiveNewsletterToUsers < ActiveRecord::Migration end </ruby> +NOTE: Some "caveats":#using-models-in-your-migrations apply to using models in your migrations. + This migration adds a +receive_newsletter+ column to the +users+ table. We want it to default to +false+ for new users, but existing users are considered to have already opted in, so we use the User model to set the flag to +true+ for existing users. @@ -73,11 +75,9 @@ class CreateProducts < ActiveRecord::Migration end </ruby> -NOTE: Some "caveats":#using-models-in-your-migrations apply to using models in your migrations. - h4. Migrations are Classes -A migration is a subclass of <tt>ActiveRecord::Migration</tt> that implements two class methods: +up+ (perform the required transformations) and +down+ (revert them). +A migration is a subclass of <tt>ActiveRecord::Migration</tt> that implements two methods: +up+ (perform the required transformations) and +down+ (revert them). Active Record provides methods that perform common data definition tasks in a database independent way (you'll read about them in detail later): @@ -115,7 +115,7 @@ h4. Changing Migrations Occasionally you will make a mistake when writing a migration. If you have already run the migration then you cannot just edit the migration and run the migration again: Rails thinks it has already run the migration and so will do nothing when you run +rake db:migrate+. You must rollback the migration (for example with +rake db:rollback+), edit your migration and then run +rake db:migrate+ to run the corrected version. -In general editing existing migrations is not a good idea: you will be creating extra work for yourself and your co-workers and cause major headaches if the existing version of the migration has already been run on production machines. Instead you should write a new migration that performs the changes you require. Editing a freshly generated migration that has not yet been committed to source control (or more generally which has not been propagated beyond your development machine) is relatively harmless. Just use some common sense. +In general editing existing migrations is not a good idea: you will be creating extra work for yourself and your co-workers and cause major headaches if the existing version of the migration has already been run on production machines. Instead you should write a new migration that performs the changes you require. Editing a freshly generated migration that has not yet been committed to source control (or more generally which has not been propagated beyond your development machine) is relatively harmless. h3. Creating a Migration @@ -344,7 +344,7 @@ The +change+ method removes the need to write both +up+ and +down+ methods in th * +add_column+ * +add_index+ -* +add_timestamp+ +* +add_timestamps+ * +create_table+ * +remove_timestamps+ * +rename_column+ 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 2b79237c59..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: @@ -276,7 +276,7 @@ measurement,created_at,app,rails,ruby,platform h5(#output-profiling). Profiling -In profiling mode, performance tests can generate multiple types of outputs. The command line output is always presented but support for the others is dependant on the interpreter in use. A brief description of each type and their availability across interpreters is given below. +In profiling mode, performance tests can generate multiple types of outputs. The command line output is always presented but support for the others is dependent on the interpreter in use. A brief description of each type and their availability across interpreters is given below. h6. Command Line @@ -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. @@ -573,7 +573,7 @@ h3. Useful Links h4. Rails Plugins and Gems * "Rails Analyzer":http://rails-analyzer.rubyforge.org -* "Palmist":http://www.flyingmachinestudios.com/projects/ +* "Palmist":http://www.flyingmachinestudios.com/programming/announcing-palmist * "Rails Footnotes":https://github.com/josevalim/rails-footnotes/tree/master * "Query Reviewer":https://github.com/dsboulder/query_reviewer/tree/master diff --git a/railties/guides/source/plugins.textile b/railties/guides/source/plugins.textile index 2eb71e49c4..79bbe495bd 100644 --- a/railties/guides/source/plugins.textile +++ b/railties/guides/source/plugins.textile @@ -37,7 +37,7 @@ Use the +rails generate plugin+ command in your Rails root directory directory. See usage and options by asking for help: <shell> -$ rails generate plugin new --help +$ rails generate plugin --help </shell> h4. Or generate a gemified plugin. 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/rails_on_rack.textile b/railties/guides/source/rails_on_rack.textile index aa53aa6db6..8d5985dba8 100644 --- a/railties/guides/source/rails_on_rack.textile +++ b/railties/guides/source/rails_on_rack.textile @@ -111,11 +111,11 @@ h5. Adding a Middleware You can add a new middleware to the middleware stack using any of the following methods: -* +config.middleware.use(new_middleware, args)+ - Adds the new middleware at the bottom of the middleware stack. +* <tt>config.middleware.use(new_middleware, args)</tt> - Adds the new middleware at the bottom of the middleware stack. -* +config.middleware.insert_before(existing_middleware, new_middleware, args)+ - Adds the new middleware before the specified existing middleware in the middleware stack. +* <tt>config.middleware.insert_before(existing_middleware, new_middleware, args)</tt> - Adds the new middleware before the specified existing middleware in the middleware stack. -* +config.middleware.insert_after(existing_middleware, new_middleware, args)+ - Adds the new middleware after the specified existing middleware in the middleware stack. +* <tt>config.middleware.insert_after(existing_middleware, new_middleware, args)</tt> - Adds the new middleware after the specified existing middleware in the middleware stack. <ruby> # config/environment.rb @@ -154,20 +154,20 @@ h4. Internal Middleware Stack Much of Action Controller's functionality is implemented as Middlewares. The following table explains the purpose of each of them: |_.Middleware|_.Purpose| -|+Rack::Lock+|Sets +env["rack.multithread"]+ flag to +true+ and wraps the application within a Mutex.| +|+Rack::Lock+|Sets <tt>env["rack.multithread"]</tt> flag to +true+ and wraps the application within a Mutex.| |+ActionController::Failsafe+|Returns HTTP Status +500+ to the client if an exception gets raised while dispatching.| |+ActiveRecord::QueryCache+|Enables the Active Record query cache.| |+ActionController::Session::CookieStore+|Uses the cookie based session store.| |+ActionController::Session::MemCacheStore+|Uses the memcached based session store.| |+ActiveRecord::SessionStore+|Uses the database based session store.| -|+Rack::MethodOverride+|Sets HTTP method based on +_method+ parameter or +env["HTTP_X_HTTP_METHOD_OVERRIDE"]+.| +|+Rack::MethodOverride+|Sets HTTP method based on +_method+ parameter or <tt>env["HTTP_X_HTTP_METHOD_OVERRIDE"]</tt>.| |+Rack::Head+|Discards the response body if the client sends a +HEAD+ request.| TIP: It's possible to use any of the above middlewares in your custom Rack stack. h4. Customizing Internal Middleware Stack -It's possible to replace the entire middleware stack with a custom stack using +ActionController::Dispatcher.middleware=+. +It's possible to replace the entire middleware stack with a custom stack using <tt>ActionController::Dispatcher.middleware=</tt>. Put the following in an initializer: diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile index 08615bed4e..1cbc5c8f6e 100644 --- a/railties/guides/source/routing.textile +++ b/railties/guides/source/routing.textile @@ -288,7 +288,7 @@ When using +magazine_ad_path+, you can pass in instances of +Magazine+ and +Ad+ You can also use +url_for+ with a set of objects, and Rails will automatically determine which route you want: <erb> -<%= link_to "Ad details", url_for(@magazine, @ad) %> +<%= link_to "Ad details", url_for([@magazine, @ad]) %> </erb> In this case, Rails will see that +@magazine+ is a +Magazine+ and +@ad+ is an +Ad+ and will therefore use the +magazine_ad_path+ helper. In helpers like +link_to+, you can specify just the object in place of the full +url_for+ call: diff --git a/railties/guides/source/ruby_on_rails_guides_guidelines.textile b/railties/guides/source/ruby_on_rails_guides_guidelines.textile index 9ae360a73b..5989191b5c 100644 --- a/railties/guides/source/ruby_on_rails_guides_guidelines.textile +++ b/railties/guides/source/ruby_on_rails_guides_guidelines.textile @@ -65,7 +65,7 @@ It is also recommended that you work with +WARNINGS=1+. This detects duplicate I If you want to generate guides in languages other than English, you can keep them in a separate directory under +source+ (eg. <tt>source/es</tt>) and use the +GUIDES_LANGUAGE+ environment variable: <plain> -rake generate_guides GUIDES_LANGUAGE=es +bundle exec rake generate_guides GUIDES_LANGUAGE=es </plain> h3. HTML Validation @@ -73,7 +73,7 @@ h3. HTML Validation Please validate the generated HTML with: <plain> -rake validate_guides +bundle exec rake validate_guides </plain> Particularly, titles get an ID generated from their content and this often leads to duplicates. Please set +WARNINGS=1+ when generating guides to detect them. The warning messages suggest a way to fix them. diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile index 412ded1d0b..908f3f125a 100644 --- a/railties/guides/source/security.textile +++ b/railties/guides/source/security.textile @@ -649,7 +649,7 @@ h5(#sql-injection-introduction). Introduction SQL injection attacks aim at influencing database queries by manipulating web application parameters. A popular goal of SQL injection attacks is to bypass authorization. Another goal is to carry out data manipulation or reading arbitrary data. Here is an example of how not to use user input data in a query: <ruby> -Project.all(:conditions => "name = '#{params[:name]}'") +Project.where("name = '#{params[:name]}'") </ruby> This could be in a search action and the user may enter a project's name that he wants to find. If a malicious user enters ' OR 1 --, the resulting SQL query will be: @@ -681,7 +681,7 @@ h5. Unauthorized Reading The UNION statement connects two SQL queries and returns the data in one set. An attacker can use it to read arbitrary data from the database. Let's take the example from above: <ruby> -Project.all(:conditions => "name = '#{params[:name]}'") +Project.where("name = '#{params[:name]}'") </ruby> And now let's inject another query using the UNION statement: @@ -703,18 +703,18 @@ Also, the second query renames some columns with the AS statement so that the we h5(#sql-injection-countermeasures). Countermeasures -Ruby on Rails has a built-in filter for special SQL characters, which will escape ' , " , NULL character and line breaks. <em class="highlight">Using +Model.find(id)+ or +Model.find_by_some thing(something)+ automatically applies this countermeasure</em>. But in SQL fragments, especially <em class="highlight">in conditions fragments (+:conditions => "..."+), the +connection.execute()+ or +Model.find_by_sql()+ methods, it has to be applied manually</em>. +Ruby on Rails has a built-in filter for special SQL characters, which will escape ' , " , NULL character and line breaks. <em class="highlight">Using +Model.find(id)+ or +Model.find_by_some thing(something)+ automatically applies this countermeasure</em>. But in SQL fragments, especially <em class="highlight">in conditions fragments (+where("...")+), the +connection.execute()+ or +Model.find_by_sql()+ methods, it has to be applied manually</em>. Instead of passing a string to the conditions option, you can pass an array to sanitize tainted strings like this: <ruby> -Model.first(:conditions => ["login = ? AND password = ?", entered_user_name, entered_password]) +Model.where("login = ? AND password = ?", entered_user_name, entered_password).first </ruby> As you can see, the first part of the array is an SQL fragment with question marks. The sanitized versions of the variables in the second part of the array replace the question marks. Or you can pass a hash for the same result: <ruby> -Model.first(:conditions => {:login => entered_user_name, :password => entered_password}) +Model.where(:login => entered_user_name, :password => entered_password).first </ruby> The array or hash form is only available in model instances. You can try +sanitize_sql()+ elsewhere. _(highlight)Make it a habit to think about the security consequences when using an external string in SQL_. diff --git a/railties/guides/source/testing.textile b/railties/guides/source/testing.textile index 7a93c3a1e6..cc55d1f756 100644 --- a/railties/guides/source/testing.textile +++ b/railties/guides/source/testing.textile @@ -929,8 +929,8 @@ class UserControllerTest < ActionController::TestCase end invite_email = ActionMailer::Base.deliveries.first - assert_equal invite_email.subject, "You have been invited by me@example.com" - assert_equal invite_email.to[0], 'friend@example.com' + assert_equal "You have been invited by me@example.com", invite_email.subject + assert_equal 'friend@example.com', invite_email.to[0] assert_match /Hi friend@example.com/, invite_email.body end end @@ -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..603ede3d1e 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -4,6 +4,7 @@ require 'pathname' require 'active_support' require 'active_support/core_ext/kernel/reporting' +require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/logger' require 'rails/application' @@ -87,6 +88,31 @@ module Rails RAILS_CACHE end + # Returns all rails groups for loading based on: + # + # * The Rails environment; + # * The environment variable RAILS_GROUPS; + # * The optional envs given as argument and the hash with group dependencies; + # + # == Examples + # + # groups :assets => [:development, :test] + # + # # Returns + # # => [:default, :development, :assets] for Rails.env == "development" + # # => [:default, :production] for Rails.env == "production" + # + def groups(*groups) + hash = groups.extract_options! + env = Rails.env + groups.unshift(: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.uniq! + 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 953233d774..559739b2ec 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,26 +96,25 @@ module Rails def load_tasks(app=self) initialize_tasks - railties.all { |r| r.load_tasks(app) } - super - self - end - - def load_generators(app=self) - initialize_generators - railties.all { |r| r.load_generators(app) } - Rails::Generators.configure!(app.config.generators) super self end def load_console(app=self) initialize_console - railties.all { |r| r.load_console(app) } super self end + # Rails.application.env_config stores some of the Rails initial environment parameters. + # Currently stores: + # + # * action_dispatch.parameter_filter" => config.filter_parameters, + # * action_dispatch.secret_token" => config.secret_token, + # * action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions + # + # These parameters will be used by middlewares and engines to configure themselves. + # def env_config @env_config ||= super.merge({ "action_dispatch.parameter_filter" => config.filter_parameters, @@ -138,6 +133,10 @@ module Rails @config ||= Application::Configuration.new(find_root_with_flag("config.ru", Dir.pwd)) end + def to_app + self + end + protected alias :build_middleware_stack :app @@ -195,10 +194,6 @@ module Rails end end - def initialize_generators - require "rails/generators" - end - def initialize_console require "pp" require "rails/console/app" diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 3b74de690a..975e159999 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -30,13 +30,15 @@ module Rails @log_level = nil @middleware = app_middleware @generators = app_generators + @cache_store = [ :file_store, "#{root}/tmp/cache/" ] @assets = ActiveSupport::OrderedOptions.new @assets.enabled = false @assets.paths = [] - @assets.precompile = [ /\w+\.(?!js|css).+/, "application.js", "application.css" ] + @assets.precompile = [ /\w+\.(?!js|css).+/, /application.(css|js)$/ ] @assets.prefix = "/assets" + @assets.cache_store = [ :file_store, "#{root}/tmp/cache/assets/" ] @assets.js_compressor = nil @assets.css_compressor = nil end @@ -70,7 +72,6 @@ module Rails paths.add "public/javascripts" paths.add "public/stylesheets" paths.add "tmp" - paths.add "tmp/cache" paths end end @@ -95,16 +96,6 @@ module Rails YAML::load(ERB.new(IO.read(paths["config/database"].first)).result) end - def cache_store - @cache_store ||= begin - if File.exist?("#{root}/tmp/cache/") - [ :file_store, "#{root}/tmp/cache/" ] - else - :memory_store - end - end - end - def log_level @log_level ||= Rails.env.production? ? :info : :debug 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.rb b/railties/lib/rails/commands.rb index 39627a3094..5e55aeada9 100644 --- a/railties/lib/rails/commands.rb +++ b/railties/lib/rails/commands.rb @@ -23,11 +23,7 @@ when 'generate', 'destroy', 'plugin' require APP_PATH Rails.application.require_environment! - if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH) - Rails.application.load_generators(engine) - else - Rails.application.load_generators - end + Rails.application.load_generators require "rails/commands/#{command}" end @@ -95,7 +91,7 @@ In addition to those, there are: benchmarker See how fast a piece of code runs profiler Get profile information from a piece of code plugin Install a plugin - runner Run a piece of code in the application environment + runner Run a piece of code in the application environment (short-cut alias: "r") All commands can be run with -h for more information. EOT diff --git a/railties/lib/rails/commands/benchmarker.rb b/railties/lib/rails/commands/benchmarker.rb index b06c915ac3..6c52d0f70f 100644 --- a/railties/lib/rails/commands/benchmarker.rb +++ b/railties/lib/rails/commands/benchmarker.rb @@ -9,7 +9,7 @@ ARGV.pop def options options = {} defaults = ActiveSupport::Testing::Performance::DEFAULTS - + OptionParser.new do |opt| opt.banner = "Usage: rails benchmarker 'Ruby.code' 'Ruby.more_code' ... [OPTS]" opt.on('-r', '--runs N', Numeric, 'Number of runs.', "Default: #{defaults[:runs]}") { |r| options[:runs] = r } @@ -17,13 +17,13 @@ def options opt.on('-m', '--metrics a,b,c', Array, 'Metrics to use.', "Default: #{defaults[:metrics].join(",")}") { |m| options[:metrics] = m.map(&:to_sym) } opt.parse!(ARGV) end - + options end class BenchmarkerTest < ActionDispatch::PerformanceTest self.profile_options = options - + ARGV.each do |expression| eval <<-RUBY def test_#{expression.parameterize('_')} diff --git a/railties/lib/rails/commands/generate.rb b/railties/lib/rails/commands/generate.rb index b6f9a003d1..1fb2d98834 100644 --- a/railties/lib/rails/commands/generate.rb +++ b/railties/lib/rails/commands/generate.rb @@ -7,4 +7,6 @@ if ARGV.first.in?([nil, "-h", "--help"]) end name = ARGV.shift -Rails::Generators.invoke name, ARGV, :behavior => :invoke, :destination_root => Rails.root + +root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root +Rails::Generators.invoke name, ARGV, :behavior => :invoke, :destination_root => root 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/commands/profiler.rb b/railties/lib/rails/commands/profiler.rb index 94cf32d32d..ea6347c918 100644 --- a/railties/lib/rails/commands/profiler.rb +++ b/railties/lib/rails/commands/profiler.rb @@ -6,7 +6,7 @@ require 'active_support/testing/performance' def options options = {} defaults = ActiveSupport::Testing::Performance::DEFAULTS - + OptionParser.new do |opt| opt.banner = "Usage: rails benchmarker 'Ruby.code' 'Ruby.more_code' ... [OPTS]" opt.on('-r', '--runs N', Numeric, 'Number of runs.', "Default: #{defaults[:runs]}") { |r| options[:runs] = r } @@ -15,13 +15,13 @@ def options opt.on('-f', '--formats x,y,z', Array, 'Formats to output to.', "Default: #{defaults[:formats].join(",")}") { |m| options[:formats] = m.map(&:to_sym) } opt.parse!(ARGV) end - + options end class ProfilerTest < ActionDispatch::PerformanceTest self.profile_options = options - + ARGV.each do |expression| eval <<-RUBY def test_#{expression.parameterize('_')} diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb index f8b00e7249..e8cc5d9e3b 100644 --- a/railties/lib/rails/commands/runner.rb +++ b/railties/lib/rails/commands/runner.rb @@ -4,6 +4,10 @@ require 'rbconfig' options = { :environment => (ENV['RAILS_ENV'] || "development").dup } code_or_file = nil +if ARGV.first.nil? + ARGV.push "-h" +end + ARGV.clone.options do |opts| script_name = File.basename($0) opts.banner = "Usage: runner [options] ('Some.ruby(code)' or a filename)" diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb index 91c87514cf..23392276d5 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -43,7 +43,7 @@ module Rails end def app - @app ||= super.instance + @app ||= super.respond_to?(:to_app) ? super.to_app : super end def opt_parser diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 383be1802f..eb6fcd5dd7 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -330,6 +330,14 @@ module Rails autoload :Configuration, "rails/engine/configuration" autoload :Railties, "rails/engine/railties" + def load_generators(app=self) + initialize_generators + railties.all { |r| r.load_generators(app) } + Rails::Generators.configure!(app.config.generators) + super + self + end + class << self attr_accessor :called_from, :isolated alias :isolated? :isolated @@ -387,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| @@ -522,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| @@ -567,12 +577,16 @@ module Rails protected + def initialize_generators + require "rails/generators" + end + def routes? defined?(@routes) end def has_migrations? - paths["db/migrate"].first.present? + paths["db/migrate"].existent.any? end def find_root_with_flag(flag, default=nil) diff --git a/railties/lib/rails/engine/commands.rb b/railties/lib/rails/engine/commands.rb new file mode 100644 index 0000000000..3b0920e213 --- /dev/null +++ b/railties/lib/rails/engine/commands.rb @@ -0,0 +1,38 @@ +require 'active_support/core_ext/object/inclusion' + +ARGV << '--help' if ARGV.empty? + +aliases = { + "g" => "generate" +} + +command = ARGV.shift +command = aliases[command] || command + +require ENGINE_PATH +engine = ::Rails::Engine.find(ENGINE_ROOT) + +case command +when 'generate', 'destroy' + require 'rails/generators' + Rails::Generators.namespace = engine.railtie_namespace + engine.load_generators + require "rails/commands/#{command}" + +when '--version', '-v' + ARGV.unshift '--version' + require 'rails/commands/application' + +else + puts "Error: Command not recognized" unless command.in?(['-h', '--help']) + puts <<-EOT +Usage: rails COMMAND [ARGS] + +The common rails commands available for engines are: + generate Generate new code (short-cut alias: "g") + destroy Undo code generated with "generate" + +All commands can be run with -h for more information. + EOT + exit(1) +end diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 09e505a75b..fdafc91fcb 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -20,6 +20,8 @@ module Rails autoload :ResourceHelpers, 'rails/generators/resource_helpers' autoload :TestCase, 'rails/generators/test_case' + mattr_accessor :namespace + DEFAULT_ALIASES = { :rails => { :actions => '-a', @@ -51,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, @@ -297,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 7972c72c1e..bbdd000ad9 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,18 +198,34 @@ module Rails end end - def gem_for_javascript + def assets_gemfile_entry + <<-GEMFILE.strip_heredoc + group :assets do + gem 'sass-rails', :git => 'git://github.com/rails/sass-rails' + gem 'coffee-rails', :git => 'git://github.com/rails/coffee-rails' + gem 'uglifier' + end + GEMFILE + end + + def javascript_gemfile_entry "gem '#{options[:javascript]}-rails'" unless options[:skip_javascript] end def bundle_command(command) - require 'bundler' - require 'bundler/cli' - say_status :run, "bundle #{command}" - Bundler::CLI.new.send(command) - rescue - say_status :failure, "bundler raised an exception, are you offline?", :red + + # We are going to shell out rather than invoking Bundler::CLI.new(command) + # because `rails new` loads the Thor gem and on the other hand bundler uses + # its own vendored Thor, which could be a different version. Running both + # things in the same process is a recipe for a night with paracetamol. + # + # We use backticks and #print here instead of vanilla #system because it + # is easier to silence stdout in the existing test suite this way. The + # end-user gets the bundler commands called anyway, so no big deal. + # + # Thanks to James Tucker for the Gem tricks involved in this call. + print `"#{Gem.ruby}" -rubygems "#{Gem.bin_path('bundler', 'bundle')}" #{command}` end def run_bundle 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..816d82cac3 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 @@ -32,7 +32,7 @@ module Rails when :decimal then "9.99" when :datetime, :timestamp, :time then Time.now.to_s(:db) when :date then Date.today.to_s(:db) - when :string then "MyString" + when :string then name == "type" ? "" : "MyString" when :text then "MyText" when :boolean then false when :references, :belongs_to then nil 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/named_base.rb b/railties/lib/rails/generators/named_base.rb index 7e7f8d2d08..c6c0392f43 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -63,9 +63,7 @@ module Rails end def namespace - @namespace ||= if defined?(Rails) && Rails.application - Rails.application.class.parents.detect { |n| n.respond_to?(:_railtie) } - end + Rails::Generators.namespace end def namespaced? diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 242677cc65..d9dd33518a 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 @@ -116,7 +117,8 @@ module Rails end def tmp - empty_directory_with_gitkeep "tmp/cache" + empty_directory "tmp/cache" + empty_directory "tmp/cache/assets" end def vendor diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index ebe38bf8e6..88eea40b1b 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -5,13 +5,13 @@ 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. +<%= assets_gemfile_entry %> + +<%= javascript_gemfile_entry %> # Use unicorn as the web server # gem 'unicorn' @@ -20,6 +20,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..500693a568 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_test_unit %> require "rails/test_unit/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) 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..fe9466b366 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. +# PostgreSQL. Versions 8.2 and up are supported. # # 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/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml index 467dfc3956..f08f86aac3 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml @@ -1,4 +1,4 @@ -# PostgreSQL. Versions 7.4 and 8.x are supported. +# PostgreSQL. Versions 8.2 and up are supported. # # Install the pg driver: # gem install pg diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index ffd9058238..3e0c29a587 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -22,6 +22,11 @@ # Only use best-standards-support built into browsers config.action_dispatch.best_standards_support = :builtin + <%- unless options.skip_active_record? -%> + # Raise exception on mass assignment protection for ActiveRecord models + config.active_record.mass_assignment_sanitizer = :strict + <%- end -%> + # Do not compress assets config.assets.compress = false end 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/initializers/inflections.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb index 9e8b0131f8..5d8d9be237 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb @@ -8,3 +8,8 @@ # inflect.irregular 'person', 'people' # inflect.uncountable %w( fish sheep ) # end +# +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt index e56195da80..fa1548db8b 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt @@ -1,7 +1,7 @@ # Be sure to restart your server when you modify this file. # -# This file contains the settings for ActionController::ParametersWrapper -# which will be enabled by default in the upcoming version of Ruby on Rails. +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. ActionController::Base.wrap_parameters <%= key_value :format, "[:json]" %> 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/app/templates/test/performance/browsing_test.rb b/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb index 5d1be041a5..3fea27b916 100644 --- a/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb +++ b/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb @@ -5,7 +5,7 @@ class BrowsingTest < ActionDispatch::PerformanceTest # Refer to the documentation for all available options # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory] # :output => 'tmp/performance', :formats => [:flat] } - + def test_homepage get '/' end diff --git a/railties/lib/rails/generators/rails/assets/USAGE b/railties/lib/rails/generators/rails/assets/USAGE index c5375cdc06..d2e5ed4482 100644 --- a/railties/lib/rails/generators/rails/assets/USAGE +++ b/railties/lib/rails/generators/rails/assets/USAGE @@ -7,7 +7,7 @@ Description: This generates a JavaScript stub in app/assets/javascripts and a stylesheet stub in app/assets/stylesheets. - + If CoffeeScript is available, JavaScripts will be generated with the .coffee extension. If Sass 3 is available, stylesheets will be generated with the .scss extension. 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/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb index 11867a4cd7..7c0a2b9cf4 100644 --- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb +++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb @@ -11,15 +11,15 @@ module Rails def app if mountable? directory "app" - template "#{app_templates_dir}/app/views/layouts/application.html.erb.tt", + template "app/views/layouts/application.html.erb.tt", "app/views/layouts/#{name}/application.html.erb" - empty_directory_with_gitkeep "app/assets/images" + empty_directory_with_gitkeep "app/assets/images/#{name}" elsif full? empty_directory_with_gitkeep "app/models" empty_directory_with_gitkeep "app/controllers" empty_directory_with_gitkeep "app/views" empty_directory_with_gitkeep "app/helpers" - empty_directory_with_gitkeep "app/assets/images" + empty_directory_with_gitkeep "app/assets/images/#{name}" end end @@ -108,9 +108,9 @@ task :default => :test def stylesheets if mountable? copy_file "#{app_templates_dir}/app/assets/stylesheets/application.css", - "app/assets/stylesheets/application.css" + "app/assets/stylesheets/#{name}/application.css" elsif full? - empty_directory_with_gitkeep "app/assets/stylesheets" + empty_directory_with_gitkeep "app/assets/stylesheets/#{name}" end end @@ -119,13 +119,15 @@ task :default => :test if mountable? template "#{app_templates_dir}/app/assets/javascripts/application.js.tt", - "app/assets/javascripts/application.js" + "app/assets/javascripts/#{name}/application.js" elsif full? - empty_directory_with_gitkeep "app/assets/javascripts" + empty_directory_with_gitkeep "app/assets/javascripts/#{name}" end end def script(force = false) + return unless full? + directory "script", :force => force do |content| "#{shebang}\n" + content end 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/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/application.html.erb.tt new file mode 100644 index 0000000000..01550dec2f --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/application.html.erb.tt @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> +<head> + <title><%= camelized %></title> + <%%= stylesheet_link_tag "<%= name %>/application" %> + <%%= javascript_include_tag "<%= name %>/application" %> + <%%= csrf_meta_tags %> +</head> +<body> + +<%%= yield %> + +</body> +</html> 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..996ea79e67 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/rails/plugin_new/templates/script/rails.tt b/railties/lib/rails/generators/rails/plugin_new/templates/script/rails.tt index 65d82abf6d..aa87d1b50c 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/script/rails.tt +++ b/railties/lib/rails/generators/rails/plugin_new/templates/script/rails.tt @@ -1,4 +1,7 @@ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. -ENGINE_PATH = File.expand_path('../..', __FILE__) -load File.expand_path('../../<%= dummy_path %>/script/rails', __FILE__) +ENGINE_ROOT = File.expand_path('../..', __FILE__) +ENGINE_PATH = File.expand_path('../../lib/<%= name -%>/engine', __FILE__) + +require 'rails/all' +require 'rails/engine/commands' 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/generators/test_unit/performance/templates/performance_test.rb b/railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb index 14a878328b..d296b26b16 100644 --- a/railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb +++ b/railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb @@ -5,7 +5,7 @@ class <%= class_name %>Test < ActionDispatch::PerformanceTest # Refer to the documentation for all available options # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory] # :output => 'tmp/performance', :formats => [:flat] } - + def test_homepage get '/' 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/paths.rb b/railties/lib/rails/paths.rb index 09ff0ef378..3f1d2ca87d 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -157,7 +157,7 @@ module Rails path = File.expand_path(p, @root.path) if @glob - result.concat Dir[File.join(path, @glob)] + result.concat Dir[File.join(path, @glob)].sort else result << path end diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index 65c567d72f..8c88b25617 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -1,6 +1,7 @@ require 'rails/initializable' require 'rails/configuration' require 'active_support/inflector' +require 'active_support/core_ext/module/introspection' module Rails # Railtie is the core of the Rails framework and provides several hooks to extend @@ -83,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 @@ -173,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) } @@ -189,8 +190,12 @@ module Rails end end - def load_generators(app) + def load_generators(app=self) self.class.generators.each { |block| block.call(app) } end + + def railtie_namespace + @railtie_namespace ||= self.class.parents.detect { |n| n.respond_to?(:_railtie) } + end end end diff --git a/railties/lib/rails/railtie/configurable.rb b/railties/lib/rails/railtie/configurable.rb index 920ab67ff1..53796c74cf 100644 --- a/railties/lib/rails/railtie/configurable.rb +++ b/railties/lib/rails/railtie/configurable.rb @@ -1,3 +1,5 @@ +require 'active_support/concern' + module Rails class Railtie module Configurable diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb index 6d6e7f8b59..1eed763aa3 100644 --- a/railties/lib/rails/source_annotation_extractor.rb +++ b/railties/lib/rails/source_annotation_extractor.rb @@ -28,9 +28,9 @@ class SourceAnnotationExtractor end end - # Prints all annotations with tag +tag+ under the root directories +app+, +lib+, - # and +test+ (recursively). Only filenames with extension +.builder+, +.rb+, - # +.rxml+, +.rhtml+, or +.erb+ are taken into account. The +options+ + # Prints all annotations with tag +tag+ under the root directories +app+, +config+, +lib+, + # +script+, and +test+ (recursively). Only filenames with extension + # +.builder+, +.rb+, +.rxml+, +.rhtml+, or +.erb+ are taken into account. The +options+ # hash is passed to each annotation's +to_s+. # # This class method is the single entry point for the rake tasks. @@ -49,7 +49,7 @@ class SourceAnnotationExtractor # with their annotations. Only files with annotations are included, and only # those with extension +.builder+, +.rb+, +.rxml+, +.rhtml+, and +.erb+ # are taken into account. - def find(dirs=%w(app lib test)) + def find(dirs=%w(app config lib script test)) dirs.inject({}) { |h, dir| h.update(find_in(dir)) } 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 5f87edc9e2..0000000000 --- a/railties/lib/rails/tasks/assets.rake +++ /dev/null @@ -1,18 +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{ |file| rm file } - end -end diff --git a/railties/lib/rails/tasks/engine.rake b/railties/lib/rails/tasks/engine.rake index 2f0e7be896..2152e811f5 100644 --- a/railties/lib/rails/tasks/engine.rake +++ b/railties/lib/rails/tasks/engine.rake @@ -67,3 +67,5 @@ def find_engine_path(path) find_engine_path(File.expand_path('..', path)) end end + +Rake.application.invoke_task(:load_app) diff --git a/railties/lib/rails/tasks/tmp.rake b/railties/lib/rails/tasks/tmp.rake index 3ee5452475..0d6c10328f 100644 --- a/railties/lib/rails/tasks/tmp.rake +++ b/railties/lib/rails/tasks/tmp.rake @@ -4,7 +4,7 @@ namespace :tmp do desc "Creates tmp directories for sessions, cache, sockets, and pids" task :create do - FileUtils.mkdir_p(%w( tmp/sessions tmp/cache tmp/sockets tmp/pids )) + FileUtils.mkdir_p(%w( tmp/sessions tmp/cache tmp/sockets tmp/pids tmp/cache/assets )) end namespace :sessions do 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/lib/rails/version.rb b/railties/lib/rails/version.rb index 3d6ecb9d30..254227ecf7 100644 --- a/railties/lib/rails/version.rb +++ b/railties/lib/rails/version.rb @@ -1,9 +1,9 @@ module Rails module VERSION #:nodoc: MAJOR = 3 - MINOR = 1 + MINOR = 2 TINY = 0 - PRE = "rc1" + PRE = "beta" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/railties/railties.gemspec b/railties/railties.gemspec index f1c92bd5d4..b917128885 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -15,11 +15,15 @@ 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') s.add_dependency('thor', '~> 0.14.6') s.add_dependency('rack-ssl', '~> 1.3.2') + s.add_dependency('rdoc', '~> 3.4') s.add_dependency('activesupport', version) s.add_dependency('actionpack', version) end diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb index 8b38081667..1c3f8a701a 100644 --- a/railties/test/abstract_unit.rb +++ b/railties/test/abstract_unit.rb @@ -10,7 +10,6 @@ require 'active_support/core_ext/logger' require 'action_controller' require 'rails/all' -# TODO: Remove these hacks module TestApp class Application < Rails::Application config.root = File.dirname(__FILE__) diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb index 2b593005a2..e1eee71a9e 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,45 @@ module ApplicationTests assert_match "alert()", last_response.body end + test "assets do not require compressors until it is used" do + app_file "app/assets/javascripts/demo.js.erb", "<%= :alert %>();" + ENV["RAILS_ENV"] = "production" + require "#{app_path}/config/environment" + + assert !defined?(Uglifier) + get "/assets/demo.js" + assert_match "alert()", last_response.body + assert defined?(Uglifier) + end + + test "assets are compiled properly" do + app_file "app/assets/javascripts/application.js", "alert();" + app_file "app/assets/javascripts/foo/application.js", "alert();" + + capture(:stdout) do + Dir.chdir(app_path){ `bundle exec rake assets:precompile` } + end + files = Dir["#{app_path}/public/assets/application-*.js"] + files << Dir["#{app_path}/public/assets/foo/application-*.js"].first + files.each do |file| + assert_not_nil file, "Expected application.js asset to be generated, but none found" + assert_equal "alert();\n", File.read(file) + end + 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 e2c996364f..448982f9de 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -36,10 +36,22 @@ module ApplicationTests def teardown teardown_app + FileUtils.rm_rf(new_app) if File.directory?(new_app) end - def teardown - FileUtils.rm_rf(new_app) if File.directory?(new_app) + 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", :another, :assets], Rails.groups(:another, :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 @@ -504,5 +516,14 @@ module ApplicationTests get "/", { :format => :xml }, "HTTP_ACCEPT" => "application/xml" assert_equal 'XML', last_response.body end + + test "Rails.application#env_config exists and include some existing parameters" do + make_basic_app + + assert_respond_to app, :env_config + assert_equal app.env_config['action_dispatch.parameter_filter'], app.config.filter_parameters + assert_equal app.env_config['action_dispatch.secret_token'], app.config.secret_token + assert_equal app.env_config['action_dispatch.show_exceptions'], app.config.action_dispatch.show_exceptions + end end end diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb index aa4db6e12a..8c2c079fb8 100644 --- a/railties/test/application/initializers/i18n_test.rb +++ b/railties/test/application/initializers/i18n_test.rb @@ -52,7 +52,7 @@ module ApplicationTests end test "locale files should be added to the load path" do - app_file "config/another_locale.yml", "" + app_file "config/another_locale.yml", "en:\nfoo: ~" add_to_config <<-RUBY config.i18n.load_path << config.root.join("config/another_locale.yml").to_s @@ -131,7 +131,7 @@ en: # Fallbacks test "not using config.i18n.fallbacks does not initialize I18n.fallbacks" do - I18n.backend = Class.new { include I18n::Backend::Base }.new + I18n.backend = Class.new(I18n::Backend::Simple).new load_app assert_no_fallbacks end @@ -145,7 +145,7 @@ en: test "config.i18n.fallbacks = true initializes I18n.fallbacks with default settings even when backend changes" do I18n::Railtie.config.i18n.fallbacks = true - I18n::Railtie.config.i18n.backend = Class.new { include I18n::Backend::Base }.new + I18n::Railtie.config.i18n.backend = Class.new(I18n::Backend::Simple).new load_app assert I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks) assert_fallbacks :de => [:de, :en] diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb index 0d22d8c19a..964cff48cd 100644 --- a/railties/test/application/paths_test.rb +++ b/railties/test/application/paths_test.rb @@ -48,7 +48,6 @@ module ApplicationTests assert_path @paths["vendor"], "vendor" assert_path @paths["vendor/plugins"], "vendor/plugins" assert_path @paths["tmp"], "tmp" - assert_path @paths["tmp/cache"], "tmp/cache" assert_path @paths["config"], "config" assert_path @paths["config/locales"], "config/locales/en.yml" assert_path @paths["config/environment"], "config/environment.rb" diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index 44ac6615de..7671c129e9 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 @@ -125,5 +151,14 @@ module ApplicationTests assert_equal 2, ::AppTemplate::Application::Product.count assert_equal 0, ::AppTemplate::Application::User.count end + + def test_scaffold_tests_pass_by_default + content = Dir.chdir(app_path) do + `rails generate scaffold user username:string password:string` + `bundle exec rake db:migrate db:test:clone test` + end + + assert_match(/7 tests, 10 assertions, 0 failures, 0 errors/, content) + end end -end +end
\ No newline at end of file diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb index 4104bacae5..4468fa295e 100644 --- a/railties/test/application/runner_test.rb +++ b/railties/test/application/runner_test.rb @@ -22,6 +22,10 @@ module ApplicationTests teardown_app end + def test_should_include_runner_in_shebang_line_in_help_without_option + assert_match "/rails runner", Dir.chdir(app_path) { `bundle exec rails runner` } + end + def test_should_include_runner_in_shebang_line_in_help assert_match "/rails runner", Dir.chdir(app_path) { `bundle exec rails runner --help` } end 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..fb7ebaa1fa 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 @@ -34,6 +35,7 @@ DEFAULT_APP_FILES = %w( vendor/assets vendor/plugins tmp/cache + tmp/cache/assets ) class AppGeneratorTest < Rails::Generators::TestCase @@ -47,11 +49,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 +126,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 +154,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..a85829085c 100644 --- a/railties/test/generators/generated_attribute_test.rb +++ b/railties/test/generators/generated_attribute_test.rb @@ -68,6 +68,11 @@ class GeneratedAttributeTest < Rails::Generators::TestCase assert_field_default_value :string, 'MyString' end + def test_default_value_for_type + att = Rails::Generators::GeneratedAttribute.new("type", "string") + assert_equal("", att.default) + end + def test_default_value_is_text assert_field_default_value :text, 'MyText' end @@ -113,15 +118,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/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb index 17cbac0912..dd1e4bdac1 100644 --- a/railties/test/generators/namespaced_generators_test.rb +++ b/railties/test/generators/namespaced_generators_test.rb @@ -7,15 +7,7 @@ require 'rails/generators/rails/scaffold/scaffold_generator' class NamespacedGeneratorTestCase < Rails::Generators::TestCase def setup - TestApp::Application.isolate_namespace(TestApp) - end - - def teardown - if TestApp.respond_to?(:_railtie) - TestApp.singleton_class.send(:undef_method, :_railtie) - TestApp.singleton_class.send(:undef_method, :table_name_prefix) - TestApp::Application.isolated = false - end + Rails::Generators.namespace = TestApp end end diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index b272638026..4bd77ff7e3 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -12,7 +12,6 @@ DEFAULT_PLUGIN_FILES = %w( lib lib/bukkits.rb lib/tasks/bukkits_tasks.rake - script/rails test/bukkits_test.rb test/test_helper.rb test/dummy @@ -67,7 +66,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_no_file "test" end - def test_database_entry_is_assed_by_default_in_full_mode + def test_database_entry_is_generated_for_sqlite3_by_default_in_full_mode run_generator([destination_root, "--full"]) assert_file "test/dummy/config/database.yml", /sqlite/ assert_file "Gemfile", /^gem\s+["']sqlite3["']$/ @@ -102,19 +101,19 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase def test_skipping_javascripts_without_mountable_option run_generator - assert_no_file "app/assets/javascripts/application.js" + assert_no_file "app/assets/javascripts/bukkits/application.js" assert_no_file "vendor/assets/javascripts/jquery.js" assert_no_file "vendor/assets/javascripts/jquery_ujs.js" end def test_javascripts_generation run_generator [destination_root, "--mountable"] - assert_file "app/assets/javascripts/application.js" + assert_file "app/assets/javascripts/bukkits/application.js" end def test_jquery_is_the_default_javascript_library run_generator [destination_root, "--mountable"] - assert_file "app/assets/javascripts/application.js" do |contents| + assert_file "app/assets/javascripts/bukkits/application.js" do |contents| assert_match %r{^//= require jquery}, contents assert_match %r{^//= require jquery_ujs}, contents end @@ -125,7 +124,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase def test_other_javascript_libraries run_generator [destination_root, "--mountable", '-j', 'prototype'] - assert_file "app/assets/javascripts/application.js" do |contents| + assert_file "app/assets/javascripts/bukkits/application.js" do |contents| assert_match %r{^//= require prototype}, contents assert_match %r{^//= require prototype_ujs}, contents end @@ -136,7 +135,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase def test_skip_javascripts run_generator [destination_root, "--skip-javascript", "--mountable"] - assert_no_file "app/assets/javascripts/application.js" + assert_no_file "app/assets/javascripts/bukkits/application.js" assert_no_file "vendor/assets/javascripts/jquery.js" assert_no_file "vendor/assets/javascripts/jquery_ujs.js" end @@ -160,11 +159,19 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_match(/1 tests, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`) end + def test_ensure_that_migration_tasks_work_with_mountable_option + run_generator [destination_root, "--mountable"] + FileUtils.cd destination_root + quietly { system 'bundle install' } + `bundle exec rake db:migrate` + assert_equal 0, $?.exitstatus + end + def test_creating_engine_in_full_mode run_generator [destination_root, "--full"] - assert_file "app/assets/javascripts" - assert_file "app/assets/stylesheets" - assert_file "app/assets/images" + assert_file "app/assets/javascripts/bukkits" + assert_file "app/assets/stylesheets/bukkits" + assert_file "app/assets/images/bukkits" assert_file "app/models" assert_file "app/controllers" assert_file "app/views" @@ -172,6 +179,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_file "config/routes.rb", /Rails.application.routes.draw do/ assert_file "lib/bukkits/engine.rb", /module Bukkits\n class Engine < ::Rails::Engine\n end\nend/ assert_file "lib/bukkits.rb", /require "bukkits\/engine"/ + assert_file "script/rails" end def test_being_quiet_while_creating_dummy_application @@ -180,15 +188,19 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase def test_create_mountable_application_with_mountable_option run_generator [destination_root, "--mountable"] - assert_file "app/assets/javascripts" - assert_file "app/assets/stylesheets" - assert_file "app/assets/images" + assert_file "app/assets/javascripts/bukkits" + assert_file "app/assets/stylesheets/bukkits" + assert_file "app/assets/images/bukkits" assert_file "config/routes.rb", /Bukkits::Engine.routes.draw do/ assert_file "lib/bukkits/engine.rb", /isolate_namespace Bukkits/ assert_file "test/dummy/config/routes.rb", /mount Bukkits::Engine => "\/bukkits"/ assert_file "app/controllers/bukkits/application_controller.rb", /module Bukkits\n class ApplicationController < ActionController::Base/ assert_file "app/helpers/bukkits/application_helper.rb", /module Bukkits\n module ApplicationHelper/ - assert_file "app/views/layouts/bukkits/application.html.erb", /<title>Bukkits<\/title>/ + assert_file "app/views/layouts/bukkits/application.html.erb" do |contents| + assert_match "<title>Bukkits</title>", contents + assert_match /stylesheet_link_tag\s+['"]bukkits\/application['"]/, contents + assert_match /javascript_include_tag\s+['"]bukkits\/application['"]/, contents + end end def test_creating_gemspec @@ -199,8 +211,16 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_file "bukkits.gemspec", /s.version = "0.0.1"/ end + def test_usage_of_engine_commands + run_generator [destination_root, "--full"] + assert_file "script/rails", /ENGINE_PATH = File.expand_path\('..\/..\/lib\/bukkits\/engine', __FILE__\)/ + assert_file "script/rails", /ENGINE_ROOT = File.expand_path\('..\/..', __FILE__\)/ + assert_file "script/rails", /require 'rails\/all'/ + assert_file "script/rails", /require 'rails\/engine\/commands'/ + end + def test_shebang - run_generator + run_generator [destination_root, "--full"] assert_file "script/rails", /#!\/usr\/bin\/env ruby/ end @@ -254,7 +274,6 @@ class CustomPluginGeneratorTest < Rails::Generators::TestCase assert_file 'spec/dummy' assert_file 'Rakefile', /task :default => :spec/ assert_file 'Rakefile', /# spec tasks in rakefile/ - assert_file 'script/rails', %r{spec/dummy} end protected 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/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index be9aef8a41..d3074afd91 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -67,12 +67,12 @@ module SharedGeneratorTests end def test_shebang_is_added_to_rails_file - run_generator [destination_root, "--ruby", "foo/bar/baz"] + run_generator [destination_root, "--ruby", "foo/bar/baz", "--full"] assert_file "script/rails", /#!foo\/bar\/baz/ end def test_shebang_when_is_the_same_as_default_use_env - run_generator [destination_root, "--ruby", Thor::Util.ruby_command] + run_generator [destination_root, "--ruby", Thor::Util.ruby_command, "--full"] assert_file "script/rails", /#!\/usr\/bin\/env/ 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 c0233c5f90..685d1b154b 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -91,7 +91,8 @@ module TestHelpers module Generation # Build an application by invoking the generator and going through the whole stack. def build_app(options = {}) - @prev_rails_env = ENV.delete('RAILS_ENV') + @prev_rails_env = ENV['RAILS_ENV'] + ENV['RAILS_ENV'] = 'development' FileUtils.rm_rf(app_path) FileUtils.cp_r(tmp_path('app_template'), app_path) @@ -237,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 @@ -286,4 +291,4 @@ Module.new do end f.puts "require 'rails/all'" end -end +end unless defined?(RAILS_ISOLATED_ENGINE) diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 44cee2b164..0ff1e0f180 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -15,7 +15,7 @@ module RailtiesTest @plugin = engine "bukkits" do |plugin| plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine railtie_name "bukkits" end @@ -36,7 +36,7 @@ module RailtiesTest test "initializers are executed after application configuration initializers" do @plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine initializer "dummy_initializer" do end @@ -77,7 +77,7 @@ module RailtiesTest add_to_config("config.action_dispatch.show_exceptions = false") @plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine endpoint lambda { |env| [200, {'Content-Type' => 'text/html'}, ['Hello World']] } config.middleware.use ::RailtiesTest::EngineTest::Upcaser @@ -127,7 +127,7 @@ module RailtiesTest test "it provides routes as default endpoint" do @plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine end end @@ -153,7 +153,7 @@ module RailtiesTest test "engine can load its own plugins" do @plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine end end @@ -170,7 +170,7 @@ module RailtiesTest test "engine does not load plugins that already exists in application" do @plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine end end @@ -193,7 +193,7 @@ module RailtiesTest test "it loads its environment file" do @plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine end end @@ -212,7 +212,7 @@ module RailtiesTest test "it passes router in env" do @plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine endpoint lambda { |env| [200, {'Content-Type' => 'text/html'}, 'hello'] } end diff --git a/railties/test/railties/generators_test.rb b/railties/test/railties/generators_test.rb new file mode 100644 index 0000000000..1d4ba0e5b6 --- /dev/null +++ b/railties/test/railties/generators_test.rb @@ -0,0 +1,116 @@ +RAILS_ISOLATED_ENGINE = true +require "isolation/abstract_unit" + +require 'generators/generators_test_helper' +require "rails/generators/test_case" + +module RailtiesTests + class GeneratorTest < Rails::Generators::TestCase + include ActiveSupport::Testing::Isolation + + TMP_PATH = File.expand_path(File.join(File.dirname(__FILE__), *%w[.. .. tmp])) + self.destination_root = File.join(TMP_PATH, "foo_bar") + + def tmp_path(*args) + File.join(TMP_PATH, *args) + end + + def engine_path + tmp_path('foo_bar') + end + + def bundled_rails(cmd) + `bundle exec rails #{cmd}` + end + + def rails(cmd) + environment = File.expand_path('../../../../load_paths', __FILE__) + if File.exist?("#{environment}.rb") + require_environment = "-r #{environment}" + end + `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/bin/rails #{cmd}` + end + + def build_engine(is_mountable=false) + FileUtils.rm_rf(engine_path) + FileUtils.mkdir_p(engine_path) + + mountable = is_mountable ? "--mountable" : "" + + rails("plugin new #{engine_path} --full #{mountable}") + + Dir.chdir(engine_path) do + File.open("Gemfile", "w") do |f| + f.write <<-GEMFILE.gsub(/^ {12}/, '') + source "http://rubygems.org" + + gem 'rails', :path => '#{RAILS_FRAMEWORK_ROOT}' + gem 'sqlite3' + + if RUBY_VERSION < '1.9' + gem "ruby-debug", ">= 0.10.3" + end + GEMFILE + end + end + end + + def build_mountable_engine + build_engine(true) + end + + def test_controllers_are_correctly_namespaced_when_engine_is_mountable + build_mountable_engine + Dir.chdir(engine_path) do + bundled_rails("g controller topics") + assert_file "app/controllers/foo_bar/topics_controller.rb", /module FooBar\n class TopicsController/ + assert_no_file "app/controllers/topics_controller.rb" + end + end + + def test_models_are_correctly_namespaced_when_engine_is_mountable + build_mountable_engine + Dir.chdir(engine_path) do + bundled_rails("g model topic") + assert_file "app/models/foo_bar/topic.rb", /module FooBar\n class Topic/ + assert_no_file "app/models/topic.rb" + end + end + + def test_helpers_are_correctly_namespaced_when_engine_is_mountable + build_mountable_engine + Dir.chdir(engine_path) do + bundled_rails("g helper topics") + assert_file "app/helpers/foo_bar/topics_helper.rb", /module FooBar\n module TopicsHelper/ + assert_no_file "app/helpers/topics_helper.rb" + end + end + + def test_controllers_are_not_namespaced_when_engine_is_not_mountable + build_engine + Dir.chdir(engine_path) do + bundled_rails("g controller topics") + assert_file "app/controllers/topics_controller.rb", /class TopicsController/ + assert_no_file "app/controllers/foo_bar/topics_controller.rb" + end + end + + def test_models_are_not_namespaced_when_engine_is_not_mountable + build_engine + Dir.chdir(engine_path) do + bundled_rails("g model topic") + assert_file "app/models/topic.rb", /class Topic/ + assert_no_file "app/models/foo_bar/topic.rb" + end + end + + def test_helpers_are_not_namespaced_when_engine_is_not_mountable + build_engine + Dir.chdir(engine_path) do + bundled_rails("g helper topics") + assert_file "app/helpers/topics_helper.rb", /module TopicsHelper/ + assert_no_file "app/helpers/foo_bar/topics_helper.rb" + end + end + end +end diff --git a/railties/test/railties/shared_tests.rb b/railties/test/railties/shared_tests.rb index 659551d08a..9a64b7c64e 100644 --- a/railties/test/railties/shared_tests.rb +++ b/railties/test/railties/shared_tests.rb @@ -53,8 +53,11 @@ module RailtiesTest add_to_config "ActiveRecord::Base.timestamped_migrations = false" + boot_rails + railties = Rails.application.railties.all.map(&:railtie_name) + Dir.chdir(app_path) do - output = `rake bukkits:install:migrations` + output = `bundle exec rake bukkits:install:migrations` assert File.exists?("#{app_path}/db/migrate/2_create_users.rb") assert File.exists?("#{app_path}/db/migrate/3_add_last_name_to_users.rb") @@ -63,20 +66,33 @@ module RailtiesTest assert_match /NOTE: Migration 3_create_sessions.rb from bukkits has been skipped/, output assert_equal 3, Dir["#{app_path}/db/migrate/*.rb"].length - output = `rake railties:install:migrations` + output = `bundle exec rake railties:install:migrations`.split("\n") assert File.exists?("#{app_path}/db/migrate/4_create_yaffles.rb") - assert_match /NOTE: Migration 3_create_sessions.rb from bukkits has been skipped/, output - assert_match /Copied migration 4_create_yaffles.rb from acts_as_yaffle/, output - assert_no_match /2_create_users/, output + assert_no_match /2_create_users/, output.join("\n") + + yaffle_migration_order = output.index(output.detect{|o| /Copied migration 4_create_yaffles.rb from acts_as_yaffle/ =~ o }) + bukkits_migration_order = output.index(output.detect{|o| /NOTE: Migration 3_create_sessions.rb from bukkits has been skipped/ =~ o }) + assert_not_nil yaffle_migration_order, "Expected migration to be copied" + assert_not_nil bukkits_migration_order, "Expected migration to be skipped" + assert_equal (railties.index('acts_as_yaffle') > railties.index('bukkits')) , (yaffle_migration_order > bukkits_migration_order) migrations_count = Dir["#{app_path}/db/migrate/*.rb"].length - output = `rake railties:install:migrations` + output = `bundle exec rake railties:install:migrations` assert_equal migrations_count, Dir["#{app_path}/db/migrate/*.rb"].length end end + def test_no_rake_task_without_migrations + boot_rails + require 'rake' + require 'rdoc/task' + require 'rake/testtask' + Rails.application.load_tasks + assert !Rake::Task.task_defined?('bukkits:install:migrations') + end + def test_puts_its_lib_directory_on_load_path boot_rails require "another" diff --git a/version.rb b/version.rb index 3d6ecb9d30..254227ecf7 100644 --- a/version.rb +++ b/version.rb @@ -1,9 +1,9 @@ module Rails module VERSION #:nodoc: MAJOR = 3 - MINOR = 1 + MINOR = 2 TINY = 0 - PRE = "rc1" + PRE = "beta" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end |