aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile7
-rw-r--r--actionpack/CHANGELOG2
-rw-r--r--actionpack/lib/action_controller/caching/fragments.rb4
-rw-r--r--actionpack/lib/action_controller/caching/pages.rb11
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb2
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb13
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb2
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb43
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb4
-rw-r--r--actionpack/lib/action_view/asset_paths.rb3
-rw-r--r--actionpack/lib/action_view/base.rb2
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb1
-rw-r--r--actionpack/lib/action_view/helpers/atom_feed_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb25
-rw-r--r--actionpack/lib/sprockets/compressors.rb21
-rw-r--r--actionpack/lib/sprockets/helpers/rails_helper.rb45
-rw-r--r--actionpack/lib/sprockets/railtie.rb10
-rw-r--r--actionpack/test/activerecord/polymorphic_routes_test.rb8
-rw-r--r--actionpack/test/controller/mime_responds_test.rb7
-rw-r--r--actionpack/test/controller/new_base/render_streaming_test.rb2
-rw-r--r--actionpack/test/fixtures/sprockets/alternate/stylesheets/style.css1
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb5
-rw-r--r--actionpack/test/template/form_helper_test.rb125
-rw-r--r--actionpack/test/template/sprockets_helper_test.rb12
-rw-r--r--actionpack/test/template/test_case_test.rb4
-rw-r--r--activemodel/lib/active_model/serializers/json.rb22
-rw-r--r--activemodel/test/cases/serializers/json_serialization_test.rb53
-rw-r--r--activerecord/lib/active_record/associations.rb50
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb36
-rw-r--r--activerecord/lib/active_record/associations/association.rb16
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb22
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb12
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/join_helper.rb3
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb5
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb13
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb2
-rw-r--r--activerecord/lib/active_record/autosave_association.rb2
-rw-r--r--activerecord/lib/active_record/base.rb25
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb34
-rw-r--r--activerecord/lib/active_record/errors.rb2
-rw-r--r--activerecord/lib/active_record/fixtures.rb6
-rw-r--r--activerecord/lib/active_record/migration.rb10
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb16
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb8
-rw-r--r--activerecord/lib/active_record/persistence.rb7
-rw-r--r--activerecord/lib/active_record/railties/databases.rake13
-rw-r--r--activerecord/lib/active_record/reflection.rb6
-rw-r--r--activerecord/lib/active_record/relation.rb16
-rw-r--r--activerecord/lib/active_record/relation/batches.rb2
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb8
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb5
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb4
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb14
-rw-r--r--activerecord/test/cases/associations/eager_test.rb8
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb5
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb11
-rw-r--r--activerecord/test/cases/base_test.rb10
-rw-r--r--activerecord/test/cases/batches_test.rb7
-rw-r--r--activerecord/test/cases/calculations_test.rb4
-rw-r--r--activerecord/test/cases/finder_test.rb17
-rw-r--r--activerecord/test/cases/fixtures_test.rb8
-rw-r--r--activerecord/test/cases/habtm_destroy_order_test.rb10
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb8
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb8
-rw-r--r--activerecord/test/cases/primary_keys_test.rb2
-rw-r--r--activerecord/test/cases/relations_test.rb9
-rw-r--r--activerecord/test/schema/schema.rb4
-rw-r--r--activesupport/lib/active_support/benchmarkable.rb22
-rw-r--r--activesupport/lib/active_support/cache.rb54
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/conversions.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb18
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb2
-rw-r--r--activesupport/lib/active_support/testing/assertions.rb2
-rw-r--r--activesupport/test/caching_test.rb23
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb49
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb5
-rw-r--r--activesupport/test/whiny_nil_test.rb3
-rw-r--r--railties/guides/source/action_controller_overview.textile2
-rw-r--r--railties/guides/source/action_mailer_basics.textile25
-rw-r--r--railties/guides/source/active_record_querying.textile56
-rw-r--r--railties/guides/source/asset_pipeline.textile283
-rw-r--r--railties/guides/source/command_line.textile2
-rw-r--r--railties/guides/source/debugging_rails_applications.textile26
-rw-r--r--railties/guides/source/form_helpers.textile2
-rw-r--r--railties/guides/source/migrations.textile2
-rw-r--r--railties/guides/source/plugins.textile2
-rw-r--r--railties/guides/source/security.textile10
-rw-r--r--railties/lib/rails.rb9
-rw-r--r--railties/lib/rails/application.rb9
-rw-r--r--railties/lib/rails/application/configuration.rb2
-rw-r--r--railties/lib/rails/engine.rb2
-rw-r--r--railties/lib/rails/generators/app_base.rb10
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt5
-rw-r--r--railties/lib/rails/source_annotation_extractor.rb8
-rw-r--r--railties/lib/rails/tasks/engine.rake2
-rw-r--r--railties/test/application/assets_test.rb22
-rw-r--r--railties/test/application/configuration_test.rb11
-rw-r--r--railties/test/application/rake_test.rb21
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb8
-rw-r--r--railties/test/railties/generators_test.rb5
-rw-r--r--railties/test/railties/shared_tests.rb9
111 files changed, 1096 insertions, 541 deletions
diff --git a/Gemfile b/Gemfile
index 73585543b0..e1a8b5637e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -8,9 +8,13 @@ else
gem "arel", '~> 2.1.3'
end
+gem "jquery-rails"
gem "coffee-script"
gem "sass"
-gem "uglifier", ">= 1.0.0"
+
+# 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
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 05b7d13111..b0f7d0bc11 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -65,8 +65,6 @@
You can read more about this change in http://groups.google.com/group/rubyonrails-security/browse_thread/thread/2e516e7acc96c4fb
-* Added 'ActionView::Helpers::FormHelper.fields_for_with_index', similar to fields_for but allows to have access to the current iteration index [Jorge Bejar]
-
* Warn if we cannot verify CSRF token authenticity [José Valim]
* Allow AM/PM format in datetime selectors [Aditya Sanghi]
diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb
index 2bdb23c4d7..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
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/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/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 2080e9b5b9..2271470334 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -7,17 +7,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 +78,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 f3b7357e64..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
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 0bb436a476..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
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_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb
index 4dd02755d3..96d8fd0dfe 100644
--- a/actionpack/lib/action_view/asset_paths.rb
+++ b/actionpack/lib/action_view/asset_paths.rb
@@ -1,3 +1,4 @@
+require 'zlib'
require 'active_support/core_ext/file'
module ActionView
@@ -111,7 +112,7 @@ module ActionView
args << current_request if (arity > 1 || arity < 0) && has_request?
host.call(*args)
else
- (host =~ /%d/) ? host % (source.hash % 4) : host
+ (host =~ /%d/) ? host % (Zlib.crc32(source) % 4) : host
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/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 0c3f011c92..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
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 12a304b395..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,3 +1,4 @@
+require 'thread'
require 'active_support/core_ext/file'
module ActionView
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/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 41a503982a..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'
@@ -567,18 +568,13 @@ 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:
+ # When projects is already an association on Person you can use
+ # +accepts_nested_attributes_for+ to define the writer method for you:
#
- # <%= 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 %>
+ # class Person < ActiveRecord::Base
+ # has_many :projects
+ # accepts_nested_attributes_for :projects
+ # end
#
# If you want to destroy any of the associated models through the
# form, you have to enable it first using the <tt>:allow_destroy</tt>
@@ -1233,13 +1229,6 @@ 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) && record_object.extractable_options?
fields_options[:builder] ||= options[:builder]
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 63820cc76c..a95e7c0649 100644
--- a/actionpack/lib/sprockets/helpers/rails_helper.rb
+++ b/actionpack/lib/sprockets/helpers/rails_helper.rb
@@ -1,4 +1,4 @@
-require "action_view/helpers"
+require "action_view"
module Sprockets
module Helpers
@@ -9,14 +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)
- config ||= Rails.application.config
- if config.action_controller.present?
- config.action_controller.default_asset_host_protocol ||= :relative
- else
- config.default_asset_host_protocol ||= :relative
- end
- RailsHelper::AssetPaths.new(config, controller)
+ paths = RailsHelper::AssetPaths.new(config, controller)
+ paths.asset_environment = asset_environment
+ paths.asset_prefix = asset_prefix
+ paths
end
end
@@ -76,9 +74,28 @@ module Sprockets
params[:debug_assets] == 'true'
end
+ # 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
+
+ # 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:
- def compute_public_path(source, dir, ext=nil, include_host=true, protocol = nil)
- super(source, Rails.application.config.assets.prefix, ext, include_host, protocol)
+ 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
@@ -90,14 +107,14 @@ module Sprockets
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
@@ -109,10 +126,6 @@ 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.action_controller.present? ? config.action_controller.perform_caching : config.perform_caching
diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb
index d1e97c594e..c28bdc3061 100644
--- a/actionpack/lib/sprockets/railtie.rb
+++ b/actionpack/lib/sprockets/railtie.rb
@@ -1,8 +1,12 @@
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
+ config.default_asset_host_protocol = :relative
+
rake_tasks do
load "sprockets/assets.rake"
end
@@ -60,14 +64,14 @@ module Sprockets
env.logger = Rails.logger
if env.respond_to?(:cache)
- env.cache = Rails.cache
+ env.cache = 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.
- env.js_compressor = expand_js_compressor(assets.js_compressor || :uglifier)
- env.css_compressor = expand_css_compressor(assets.css_compressor)
+ env.js_compressor = LazyCompressor.new { expand_js_compressor(assets.js_compressor || :uglifier) }
+ env.css_compressor = LazyCompressor.new { expand_css_compressor(assets.css_compressor) }
end
env
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/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb
index 288682aa07..afb2d39955 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime_responds_test.rb
@@ -659,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/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/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index df61901b44..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'
@@ -689,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")
)
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index bf65a9359b..cc3d2cddf7 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -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)
diff --git a/actionpack/test/template/sprockets_helper_test.rb b/actionpack/test/template/sprockets_helper_test.rb
index 6dc9a2a743..f11d1bba15 100644
--- a/actionpack/test/template/sprockets_helper_test.rb
+++ b/actionpack/test/template/sprockets_helper_test.rb
@@ -197,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/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/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index 0405705b35..4fbccd7419 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -32,8 +32,15 @@ module ActiveModel
# # => {"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:
@@ -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/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/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 2cbfa42718..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"
#
@@ -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 daadc8aa81..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)
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 224e0095d9..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
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 7f7dec467a..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
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 6b8201973a..2131edbc20 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -19,7 +19,7 @@ module ActiveRecord
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 d094a8def3..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 = reflection.plural_name.dup
- 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 6b010064d5..a1a921bcb4 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -18,11 +18,11 @@ 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 = {})
@@ -50,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/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index e3f221c773..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
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 c2383630e0..eb4a16ecf5 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -1058,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
@@ -1709,27 +1709,24 @@ 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|
if k.include?("(")
multi_parameter_attributes << [ k, v ]
+ elsif respond_to?("#{k}=")
+ send("#{k}=", v)
else
- method_name = "#{k}="
- if respond_to?(method_name)
- method(method_name).arity == -2 ? send(method_name, v, options) : send(method_name, v)
- else
- raise(UnknownAttributeError, "unknown attribute: #{k}")
- end
+ raise(UnknownAttributeError, "unknown attribute: #{k}")
end
end
+ @mass_assignment_options = nil
assign_multiparameter_attributes(multi_parameter_attributes)
end
@@ -1894,6 +1891,14 @@ 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
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 29dd0a9ea3..a84f73c73f 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -670,7 +670,7 @@ module ActiveRecord
# 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]]
@@ -947,6 +947,23 @@ module ActiveRecord
"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
@@ -1085,21 +1102,6 @@ module ActiveRecord
end
end
- # Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+.
- # +schema_name+ is nil if not specified in +name+.
- # +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+)
- # +name+ supports the range of schema/table references understood by PostgreSQL, for example:
- #
- # * <tt>table_name</tt>
- # * <tt>"table.name"</tt>
- # * <tt>schema_name.table_name</tt>
- # * <tt>schema_name."table.name"</tt>
- # * <tt>"schema.name"."table name"</tt>
- def extract_schema_and_table(name)
- table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse
- [schema, table]
- end
-
def extract_table_ref_from_insert_sql(sql)
sql[/into\s+([^\(]*).*values\s*\(/i]
$1.strip if $1
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/migration.rb b/activerecord/lib/active_record/migration.rb
index 3d1bc5c1e0..c459846264 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -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",
@@ -181,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
@@ -227,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
@@ -247,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 4a01313c61..2eeff7e36f 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -6,7 +6,7 @@ module ActiveRecord
#
# * add_column
# * add_index
- # * add_timestamp
+ # * add_timestamps
# * create_table
# * remove_timestamps
# * rename_column
@@ -48,7 +48,7 @@ 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) # def create_table(*args)
record(:"#{method}", args) # record(:create_table, args)
@@ -93,15 +93,11 @@ module ActiveRecord
[:remove_timestamps, args]
end
- # Record all the methods called in the +change+ method of a migration.
- # This will ensure that IrreversibleMigration is raised when the corresponding
- # invert_method does not exist while the migration is rolled back.
+ # Forwards any missing method call to the \target.
def method_missing(method, *args, &block)
- if delegate.respond_to?(method)
- delegate.send(method, *args, &block)
- else
- record(method, args)
- end
+ @delegate.send(method, *args, &block)
+ rescue NoMethodError => e
+ raise e, e.message.sub(/ for #<.*$/, " via proxy for #{@delegate}")
end
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index f51fd21077..53617059d0 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -276,15 +276,15 @@ module ActiveRecord
type = (reflection.collection? ? :collection : :one_to_one)
- # def pirate_attributes=(attributes, assignment_opts = {})
- # assign_nested_attributes_for_one_to_one_association(:pirate, attributes, assignment_opts)
+ # def pirate_attributes=(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, assignment_opts = {})
- assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, assignment_opts)
+ def #{association_name}_attributes=(attributes)
+ assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, mass_assignment_options)
end
eoruby
else
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index f425118f59..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
@@ -284,6 +286,11 @@ module ActiveRecord
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
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 399f3c5d03..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
@@ -443,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'])
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 894d56b3a0..a2324039cf 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -130,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:
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 9cd146b857..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
@@ -249,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)
@@ -408,7 +408,17 @@ module ActiveRecord
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 d52b84179f..46ab67d1cf 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 9c5f603c01..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,
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 ec2e4d4dcd..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
@@ -307,7 +307,7 @@ module ActiveRecord
order_query.map do |o|
case o
- when Arel::Nodes::Ascending, Arel::Nodes::Descending
+ when Arel::Nodes::Ordering
o.reverse
when String, Symbol
o.to_s.split(',').collect do |s|
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 27e7b1a1c3..3a7f1badf0 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -229,8 +229,8 @@ class SchemaTest < ActiveRecord::TestCase
%("schema"."table_name") => %w{schema table_name},
%("even spaces".table) => ['even spaces','table'],
%(schema."table.name") => ['schema', 'table.name']
- }.each do |given,expect|
- assert_equal expect, @connection.send(:extract_schema_and_table, given)
+ }.each do |given, expect|
+ assert_equal expect, ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::Utils.extract_schema_and_table(given)
end
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 7338513b41..4190f6d76d 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -667,4 +667,18 @@ 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
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 325fc58958..f1d061133c 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -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/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 7e331658a3..a2764f3e3b 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -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
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 877148bd5e..0b1ba31ac2 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -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 a1d0dafa57..26931e3e85 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -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
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 5fef3faab9..9feb7b010f 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -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
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index 6620464d6a..50ddf6c757 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -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/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 56f6d795b6..224b3f3d1f 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -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/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 4e75eafe3d..3d2a03d2b9 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -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_test.rb b/activerecord/test/cases/fixtures_test.rb
index 306b437fb3..842e8a0049 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -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 f2b91d977e..2ce0de360e 100644
--- a/activerecord/test/cases/habtm_destroy_order_test.rb
+++ b/activerecord/test/cases/habtm_destroy_order_test.rb
@@ -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/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index c6c6079490..9e8475465e 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -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/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
index 85d38fac25..36007255fa 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -14,11 +14,9 @@ module ActiveRecord
assert recorder.respond_to?(:america)
end
- def test_non_existing_method_records_and_raises_on_inversion
- @recorder.send(:non_existing_method, :horses)
- assert_equal 1, @recorder.commands.length
- assert_raises(ActiveRecord::IrreversibleMigration) do
- @recorder.inverse
+ def test_send_calls_super
+ assert_raises(NoMethodError) do
+ @recorder.send(:non_existing_method, :horses)
end
end
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 05a41d8a0a..7e3da145e5 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -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/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 5a95e68eb4..0aaa0342b1 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -391,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/schema/schema.rb b/activerecord/test/schema/schema.rb
index 4fe311b441..e6cd274586 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -305,6 +305,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 +721,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/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/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/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/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb
index a19b5c95c7..3bf4edbdef 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -67,7 +67,7 @@ class Object
end
end
-class Fixnum
+class Numeric
def html_safe?
true
end
@@ -137,14 +137,14 @@ module ActiveSupport #:nodoc:
UNSAFE_STRING_METHODS.each do |unsafe_method|
class_eval <<-EOT, __FILE__, __LINE__
- def #{unsafe_method}(*args, &block)
- to_str.#{unsafe_method}(*args, &block)
- end
-
- def #{unsafe_method}!(*args)
- @dirty = true
- super
- end
+ 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
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/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/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/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/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 7a8cda160d..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
@@ -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?
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/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/active_record_querying.textile b/railties/guides/source/active_record_querying.textile
index e3871a3c34..082f9eda7d 100644
--- a/railties/guides/source/active_record_querying.textile
+++ b/railties/guides/source/active_record_querying.textile
@@ -256,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
@@ -466,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.
@@ -474,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
@@ -484,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 < '2011-04-27'
+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 those that are at least one month old.
+This will return single order objects for each day, but only those that are ordered more than $100 in a day.
h3. Overriding Conditions
@@ -965,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.
@@ -1146,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/asset_pipeline.textile b/railties/guides/source/asset_pipeline.textile
index 78683b743c..563c1c79ae 100644
--- a/railties/guides/source/asset_pipeline.textile
+++ b/railties/guides/source/asset_pipeline.textile
@@ -13,15 +13,82 @@ endprologue.
h3. What Is The Asset Pipeline?
-With Rails 3.1 comes a new feature known as the asset pipeline. The asset pipeline provides features that have usually been implemented by external gems, such as "Jammit":http://documentcloud.github.com/jammit and "Sprockets.":http://getsprockets.org These gems are popular for being able to serve concatenated or compressed versions of the assets of an application, such as Cascade Style Sheets (CSS) or JavaScript (JS) files so that the number of requests made to the server are reduced, making the page load faster. Rails 3.1 includes the Sprockets gem.
+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 lived under the +public+ directory in directories such as +images+, +javascripts+ and +stylesheets+. With the asset pipeline, the preferred location for these assets is now the +app/assets+ directory. Files in this directory will be served by the Sprockets middleware included in the sprockets gem.
+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.
-This is not to say that assets can (or should) no longer be placed in +public+. They still can be, they will just be served by the application or the web server which is running the application and served just like normal files. You would only use +app/assets+ if you wish your files to undergo some pre-processing before they are served.
+When a scaffold or controller is generated for the application, Rails will also generate a JavaScript file (or CoffeeScript if the +coffee-script+ gem is in the +Gemfile+) and a Cascading Style Sheet file (or SCSS if +sass-rails+ is in the +Gemfile+) file for that controller.
-When a scaffold or controller is generated for the application, Rails will also generate a JavaScript file (or CoffeeScript if the +coffee-script+ gem is in the +Gemfile+) and a Cascade Style Sheet file (or SCSS if +sass-rails+ is in the +Gemfile+) file for that controller. For example, if a +ProjectsController+ is generated, there will be a new file at +app/assets/javascripts/projects.js.coffee+ and another at +app/assets/stylesheets/projects.css.scss+. You should put any JavaScript or CSS unique to a controller inside their respective asset files.
+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
@@ -31,29 +98,34 @@ Assets can be placed inside an application in one of three locations: +app/asset
+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.
++vendor/assets+ is for assets that are owned by outside entities, such as code for JavaScript plugins.
-Any subdirectory that exists within these three locations will be added to the search path for Sprockets (visible by calling +Rails.application.config.assets.paths+ in a console). When an asset is requested, these paths will be looked through to see if they contain an asset matching the name specified. Once an asset has been found, it's processed by Sprockets and then served up.
+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. External Assets
+h4. Coding links to Assets
-Assets can also come from external sources such as engines. A good example of this is the +jquery-rails+ gem which comes with Rails as the standard JavaScript library gem. This gem contains an engine class which inherits from +Rails::Engine+. By doing this, Rails is informed that the directory for this gem may contain assets and the +app/assets+, +lib/assets+ and +vendor/assets+ directories of this engine are added to the search path of Sprockets.
-
-h4. Serving Assets
-
-To serve assets, we can use the same tags that we are generally familiar with:
+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 your environment is set to +true+), this file will be served by Sprockets unless a file at +public/assets/rails.png+ exists, in which case that file will be served. Otherwise, Sprockets will look through the available paths until it finds a file that matches the name and then will serve it, first looking in the application's assets directories and then falling back to the various engines of the application.
+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+.
-Sprockets does not add any new methods to require your assets, we still use the familiar +javascript_include_tag+ and +stylesheet_link_tag+. You can use it to include from the normal public directory or the assets directory.
+<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 allows some assets to be manifest files. These manifest files require what's known as _directives_, which instruct Sprockets which files to require in order to build a single CSS or JavaScript file. With these directives, Sprockets will load the files specified, process them if necessary, concatenate them into one single file and then compress them (if +Rails.application.config.assets.compress+ is set to +true+). By serving one file rather than many, a page's load time can be greatly reduced.
+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:
@@ -63,7 +135,7 @@ For example, in the default Rails application there's a +app/assets/javascripts/
//= require_tree .
</plain>
-In JavaScript files, directives begin with +//=+. In this case, the following file is using the +require+ directive and the +require_tree+ directive. The +require+ directive tells Sprockets that we would like to require a file called +jquery.js+ that is available somewhere in the search path for Sprockets. By default, this is located inside the +vendor/assets/javascripts+ directory contained within the +jquery-rails+ gem. An identical event takes place for the +jquery_ujs+ require specified here also.
+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.
@@ -76,31 +148,188 @@ There's also a default +app/assets/stylesheets/application.css+ file which conta
*/
</plain>
-The directives that work in the JavaScript files will also work in stylesheets, obviously requiring stylesheets rather than JavaScript files. The +require_tree+ directive here works the same way as the JavaScript one, requiring all stylesheets from the current directory.
+The 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
-Based on the extensions of the assets, Sprockets will do preprocessing on the files. With the default gemset that comes with Rails, when a controller or a scaffold is generated, a CoffeeScript file and a SCSS file will be generated in place of a regular JavaScript and CSS file. The example used before was a controller called "projects", which generated an +app/assets/javascripts/projects.js.coffee+ and a +app/assets/stylesheets/projects.css.scss+ file.
+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.
-In addition to this single layer of pre-processing, we can also put on additional extensions to the end of the file in order for them to be processed using other languages first. For example, we could call our stylesheet +app/assets/stylesheets/projects.css.scss.erb+ it would first be processed as ERB, then SCSS and finally served as CSS. We could also do this with our JavaScript file, calling it +app/assets/javascripts/projects.js.coffee.erb+.
+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.
-h4. Compressing Assets
+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.
+
-The default Gemfile also includes the "uglifier":https://github.com/lautis/uglifier gem. This gem wraps "UglifierJS":https://github.com/mishoo/UglifyJS (written for NodeJS) in Ruby. It compress your code by removing white spaces and other magical things like changing your if and else statements to ternary operators when possible.
+h3. Adding Assets to Your Gems
-Sprockets also turns on "Gzip":http://en.wikipedia.org/wiki/Gzip (.gz) when possible (by checking the user's headers). Gzip is a file compression technique, much like the ever so popular "zip" file, except it's more open source friendly. Gzip should not modify the contents of the file, but simply the size of the file.
+Assets can also come from external sources in the form of gems.
-h4. Adding Assets to Your Gems
+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.
-To include your assets inside of a gem, simple package it in +lib/assets+ as you would in +app/assets+. You should append or prepend the name of your gem though, this should help avoid name conflicts with other gems or the user's application.
+h3. Making Your Library or Gem a Pre-Processor
-h4. Making Your Library or Gem a Pre-Processor
+"You should be able to register [your gems] on Tilt and Sprockets will find them." - Josh
+Tilt: https://github.com/rtomayko/tilt
-"You should be able to register [your gems] on Tilt and Sprockets will find them." - Josh
-Tilt: https://github.com/rtomayko/tilt \ No newline at end of file
diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile
index b0edc1017d..9e3b25d794 100644
--- a/railties/guides/source/command_line.textile
+++ b/railties/guides/source/command_line.textile
@@ -507,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
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 4314d00f2e..9051ede9dd 100644
--- a/railties/guides/source/form_helpers.textile
+++ b/railties/guides/source/form_helpers.textile
@@ -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.
diff --git a/railties/guides/source/migrations.textile b/railties/guides/source/migrations.textile
index f17f686d47..dbbf8f3b51 100644
--- a/railties/guides/source/migrations.textile
+++ b/railties/guides/source/migrations.textile
@@ -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/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/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/lib/rails.rb b/railties/lib/rails.rb
index df92934457..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'
@@ -91,7 +92,7 @@ module Rails
#
# * The Rails environment;
# * The environment variable RAILS_GROUPS;
- # * The optional hash given as argument with group dependencies;
+ # * The optional envs given as argument and the hash with group dependencies;
#
# == Examples
#
@@ -101,12 +102,14 @@ module Rails
# # => [:default, :development, :assets] for Rails.env == "development"
# # => [:default, :production] for Rails.env == "production"
#
- def groups(hash={})
+ def groups(*groups)
+ hash = groups.extract_options!
env = Rails.env
- groups = [:default, 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
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index fe29668c72..559739b2ec 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -106,6 +106,15 @@ module Rails
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,
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 3b74de690a..1a29483a73 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -34,7 +34,7 @@ module Rails
@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.js_compressor = nil
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index bf57a034b2..eb6fcd5dd7 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -586,7 +586,7 @@ module Rails
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/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index c8847f0159..bbdd000ad9 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -198,6 +198,16 @@ module Rails
end
end
+ 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
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index d45430e6ac..88eea40b1b 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -9,11 +9,7 @@ source 'http://rubygems.org'
# Gems used only for assets and not required
# in production environments by default.
-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
+<%= assets_gemfile_entry %>
<%= javascript_gemfile_entry %>
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 996ff4baa3..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,4 +1,4 @@
-# PostgreSQL. Versions 7.4 and 8.x are supported.
+# PostgreSQL. Versions 8.2 and up are supported.
#
# Configure Using Gemfile
# gem 'activerecord-jdbcpostgresql-adapter'
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/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/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/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index b76dae8e18..e1eee71a9e 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -35,16 +35,30 @@ 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
-
- file = Dir["#{app_path}/public/assets/application-*.js"][0]
- assert_not_nil file, "Expected application.js asset to be generated, but none found"
- assert_equal "alert();\n", File.read(file)
+ 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
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 2547863d12..448982f9de 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -45,7 +45,7 @@ module ApplicationTests
Rails.env = "development"
assert_equal [:default, "development"], Rails.groups
assert_equal [:default, "development", :assets], Rails.groups(:assets => [:development])
- assert_equal [:default, "development", :assets], Rails.groups(:assets => %w(development))
+ assert_equal [:default, "development", :another, :assets], Rails.groups(:another, :assets => %w(development))
Rails.env = "test"
assert_equal [:default, "test"], Rails.groups(:assets => [:development])
@@ -516,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/rake_test.rb b/railties/test/application/rake_test.rb
index b61e2851bf..7671c129e9 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -61,22 +61,22 @@ module ApplicationTests
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
@@ -151,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/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index be72391e58..4bd77ff7e3 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -159,6 +159,14 @@ 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/bukkits"
diff --git a/railties/test/railties/generators_test.rb b/railties/test/railties/generators_test.rb
index fe59dcd52b..1d4ba0e5b6 100644
--- a/railties/test/railties/generators_test.rb
+++ b/railties/test/railties/generators_test.rb
@@ -1,7 +1,8 @@
RAILS_ISOLATED_ENGINE = true
require "isolation/abstract_unit"
-require "#{RAILS_FRAMEWORK_ROOT}/railties/lib/rails/generators/test_case"
+require 'generators/generators_test_helper'
+require "rails/generators/test_case"
module RailtiesTests
class GeneratorTest < Rails::Generators::TestCase
@@ -31,8 +32,8 @@ module RailtiesTests
end
def build_engine(is_mountable=false)
+ FileUtils.rm_rf(engine_path)
FileUtils.mkdir_p(engine_path)
- FileUtils.rm_r(engine_path)
mountable = is_mountable ? "--mountable" : ""
diff --git a/railties/test/railties/shared_tests.rb b/railties/test/railties/shared_tests.rb
index 8cbc76db71..9a64b7c64e 100644
--- a/railties/test/railties/shared_tests.rb
+++ b/railties/test/railties/shared_tests.rb
@@ -84,6 +84,15 @@ module RailtiesTest
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"