aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--RELEASING_RAILS.rdoc6
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb20
-rw-r--r--actionpack/lib/action_dispatch/middleware/params_parser.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb16
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb4
-rw-r--r--actionpack/lib/action_view/helpers/atom_feed_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb4
-rw-r--r--actionpack/test/abstract_unit.rb4
-rw-r--r--actionpack/test/dispatch/request/json_params_parsing_test.rb16
-rw-r--r--actionpack/test/dispatch/request/xml_params_parsing_test.rb16
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb7
-rw-r--r--actionpack/test/fixtures/test/_label_with_block.erb4
-rw-r--r--actionpack/test/template/form_helper_test.rb7
-rw-r--r--activemodel/CHANGELOG.md12
-rw-r--r--activemodel/lib/active_model.rb1
-rw-r--r--activemodel/lib/active_model/callbacks.rb2
-rw-r--r--activemodel/lib/active_model/serializable.rb144
-rw-r--r--activemodel/lib/active_model/serializable/json.rb108
-rw-r--r--activemodel/lib/active_model/serializable/xml.rb195
-rw-r--r--activemodel/lib/active_model/serialization.rb139
-rw-r--r--activemodel/lib/active_model/serializer.rb253
-rw-r--r--activemodel/lib/active_model/serializers/json.rb102
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb191
-rw-r--r--activemodel/test/cases/naming_test.rb2
-rw-r--r--activemodel/test/cases/serializable/json_test.rb (renamed from activemodel/test/cases/serializers/json_serialization_test.rb)2
-rw-r--r--activemodel/test/cases/serializable/xml_test.rb (renamed from activemodel/test/cases/serializers/xml_serialization_test.rb)4
-rw-r--r--activemodel/test/cases/serializable_test.rb (renamed from activemodel/test/cases/serialization_test.rb)4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/relation.rb5
-rw-r--r--activerecord/lib/active_record/serialization.rb2
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql2/explain_test.rb3
-rw-r--r--activerecord/test/cases/adapters/postgresql/explain_test.rb3
-rw-r--r--activerecord/test/cases/adapters/sqlite3/explain_test.rb3
-rw-r--r--activeresource/lib/active_resource/custom_methods.rb2
-rw-r--r--activesupport/CHANGELOG.md3
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb47
-rw-r--r--activesupport/lib/active_support/core_ext/object/inclusion.rb24
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_json.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb37
-rw-r--r--activesupport/lib/active_support/dependencies.rb34
-rw-r--r--activesupport/lib/active_support/json/encoding.rb8
-rw-r--r--activesupport/test/class_cache_test.rb43
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb10
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb18
-rw-r--r--activesupport/test/core_ext/object/inclusion_test.rb10
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb20
-rw-r--r--railties/CHANGELOG.md14
-rw-r--r--railties/guides/source/active_record_querying.textile16
-rw-r--r--railties/guides/source/active_support_core_extensions.textile39
-rw-r--r--railties/guides/source/configuring.textile2
-rw-r--r--railties/guides/source/getting_started.textile2
-rw-r--r--railties/guides/source/initialization.textile22
-rw-r--r--railties/lib/rails/application.rb3
-rw-r--r--railties/lib/rails/application/route_inspector.rb2
-rw-r--r--railties/lib/rails/generators/actions.rb7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/index.html2
-rw-r--r--railties/lib/rails/generators/resource_helpers.rb4
-rw-r--r--railties/lib/rails/railtie.rb2
-rw-r--r--railties/lib/rails/tasks/routes.rake2
-rw-r--r--railties/test/application/configuration_test.rb1
-rw-r--r--railties/test/application/route_inspect_test.rb4
-rw-r--r--railties/test/generators/actions_test.rb8
-rw-r--r--railties/test/generators/app_generator_test.rb1
-rw-r--r--railties/test/generators/orm_test.rb38
65 files changed, 1132 insertions, 592 deletions
diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc
index 3f05e97b91..9f25adeccc 100644
--- a/RELEASING_RAILS.rdoc
+++ b/RELEASING_RAILS.rdoc
@@ -100,8 +100,8 @@ branch.
Run `rake install` to generate the gems and install them locally. Then try
generating a new app and ensure that nothing explodes.
-This will stop you looking silly when you push an RC to rubygems.org and then
-realise it is broken.
+This will stop you from looking silly when you push an RC to rubygems.org and
+then realise it is broken.
=== Release the gem.
@@ -209,7 +209,7 @@ Repeat these steps until the CI is green.
=== Manually trigger docs generation
We have a post-receive hook in GitHub that calls the docs server on pushes.
-Triggers generation and publication of edge docs, updates the contrib app,
+It triggers generation and publication of edge docs, updates the contrib app,
and generates and publishes stable docs if a new stable tag is detected.
The hook unfortunately is not invoked by tag pushing, so once the new stable
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 0ad9dbeda9..6e9ce450ac 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/object/blank'
+require 'set'
module ActionController
# See <tt>Renderers.add</tt>
@@ -12,16 +13,13 @@ module ActionController
included do
class_attribute :_renderers
- self._renderers = {}.freeze
+ self._renderers = Set.new.freeze
end
module ClassMethods
def use_renderers(*args)
- new = _renderers.dup
- args.each do |key|
- new[key] = RENDERERS[key]
- end
- self._renderers = new.freeze
+ renderers = _renderers + args
+ self._renderers = renderers.freeze
end
alias use_renderer use_renderers
end
@@ -31,10 +29,10 @@ module ActionController
end
def _handle_render_options(options)
- _renderers.each do |name, value|
- if options.key?(name.to_sym)
+ _renderers.each do |name|
+ if options.key?(name)
_process_options(options)
- return send("_render_option_#{name}", options.delete(name.to_sym), options)
+ return send("_render_option_#{name}", options.delete(name), options)
end
end
nil
@@ -42,7 +40,7 @@ module ActionController
# Hash of available renderers, mapping a renderer name to its proc.
# Default keys are :json, :js, :xml.
- RENDERERS = {}
+ RENDERERS = Set.new
# Adds a new renderer to call within controller actions.
# A renderer is invoked by passing its name as an option to
@@ -79,7 +77,7 @@ module ActionController
# <tt>ActionController::MimeResponds#respond_with</tt>
def self.add(key, &block)
define_method("_render_option_#{key}", &block)
- RENDERERS[key] = block
+ RENDERERS << key.to_sym
end
module All
diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb
index 84e3dd16dd..6ded9dbfed 100644
--- a/actionpack/lib/action_dispatch/middleware/params_parser.rb
+++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb
@@ -52,7 +52,7 @@ module ActionDispatch
false
end
rescue Exception => e # YAML, XML or Ruby code block errors
- logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
+ logger(env).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
raise e
end
@@ -68,8 +68,8 @@ module ActionDispatch
nil
end
- def logger
- defined?(Rails.logger) ? Rails.logger : Logger.new($stderr)
+ def logger(env)
+ env['action_dispatch.logger'] || Logger.new($stderr)
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 52dce4cc81..8dc2820d37 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -63,7 +63,7 @@ module ActionDispatch
private
def render_exception(env, exception)
- log_error(exception)
+ log_error(env, exception)
exception = original_exception(exception)
if env['action_dispatch.show_detailed_exceptions'] == true
@@ -124,14 +124,14 @@ module ActionDispatch
defined?(Rails.public_path) ? Rails.public_path : 'public_path'
end
- def log_error(exception)
- return unless logger
+ def log_error(env, exception)
+ return unless logger(env)
ActiveSupport::Deprecation.silence do
message = "\n#{exception.class} (#{exception.message}):\n"
message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
message << " " << application_trace(exception).join("\n ")
- logger.fatal("#{message}\n\n")
+ logger(env).fatal("#{message}\n\n")
end
end
@@ -153,8 +153,12 @@ module ActionDispatch
exception.backtrace
end
- def logger
- defined?(Rails.logger) ? Rails.logger : Logger.new($stderr)
+ def logger(env)
+ env['action_dispatch.logger'] || stderr_logger
+ end
+
+ def stderr_logger
+ Logger.new($stderr)
end
def original_exception(exception)
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 8fc8dc191b..39ba83fb9a 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -42,7 +42,7 @@ module ActionDispatch
# url_for(:controller => 'users',
# :action => 'new',
# :message => 'Welcome!',
- # :host => 'www.example.com') # Changed this.
+ # :host => 'www.example.com')
# # => "http://www.example.com/users/new?message=Welcome%21"
#
# By default, all controllers and views have access to a special version of url_for,
@@ -52,7 +52,7 @@ module ActionDispatch
#
# For convenience reasons, mailers provide a shortcut for ActionController::UrlFor#url_for.
# So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlFor#url_for'
- # in full. However, mailers don't have hostname information, and what's why you'll still
+ # in full. However, mailers don't have hostname information, and that's why you'll still
# have to specify the <tt>:host</tt> argument when generating URLs in mailers.
#
#
diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
index 39c37b25dc..90589d2238 100644
--- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
@@ -32,7 +32,7 @@ module ActionView
# app/views/posts/index.atom.builder:
# atom_feed do |feed|
# feed.title("My great blog!")
- # feed.updated(@posts.first.created_at)
+ # feed.updated(@posts.first.created_at) if @posts.any?
#
# @posts.each do |post|
# feed.entry(post) do |entry|
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index d687866d0d..4471a2b541 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -960,7 +960,7 @@ module ActionView
end
class InstanceTag
- include Helpers::CaptureHelper, Context, Helpers::TagHelper, Helpers::FormTagHelper
+ include Helpers::TagHelper, Helpers::FormTagHelper
attr_reader :object, :method_name, :object_name
@@ -992,7 +992,7 @@ module ActionView
options["for"] ||= name_and_id["id"]
if block_given?
- label_tag(name_and_id["id"], options, &block)
+ @template_object.label_tag(name_and_id["id"], options, &block)
else
content = if text.blank?
object_name.gsub!(/\[(.*)_attributes\]\[\d\]/, '.\1')
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 24d071df39..cbb8968496 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -333,9 +333,9 @@ module ActionDispatch
"#{FIXTURE_LOAD_PATH}/public"
end
- remove_method :logger
+ remove_method :stderr_logger
# Silence logger
- def logger
+ def stderr_logger
nil
end
end
diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb
index d481a2df13..ad44b4b16a 100644
--- a/actionpack/test/dispatch/request/json_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb
@@ -32,16 +32,12 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
test "logs error if parsing unsuccessful" do
with_test_routing do
- begin
- $stderr = StringIO.new
- json = "[\"person]\": {\"name\": \"David\"}}"
- post "/parse", json, {'CONTENT_TYPE' => 'application/json', 'action_dispatch.show_exceptions' => true}
- assert_response :error
- $stderr.rewind && err = $stderr.read
- assert err =~ /Error occurred while parsing request parameters/
- ensure
- $stderr = STDERR
- end
+ output = StringIO.new
+ json = "[\"person]\": {\"name\": \"David\"}}"
+ post "/parse", json, {'CONTENT_TYPE' => 'application/json', 'action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => Logger.new(output)}
+ assert_response :error
+ output.rewind && err = output.read
+ assert err =~ /Error occurred while parsing request parameters/
end
end
diff --git a/actionpack/test/dispatch/request/xml_params_parsing_test.rb b/actionpack/test/dispatch/request/xml_params_parsing_test.rb
index 65a25557b7..d8fa751548 100644
--- a/actionpack/test/dispatch/request/xml_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/xml_params_parsing_test.rb
@@ -54,16 +54,12 @@ class XmlParamsParsingTest < ActionDispatch::IntegrationTest
test "logs error if parsing unsuccessful" do
with_test_routing do
- begin
- $stderr = StringIO.new
- xml = "<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{ActiveSupport::Base64.encode64('ABC')}</avatar></pineapple>"
- post "/parse", xml, default_headers.merge('action_dispatch.show_exceptions' => true)
- assert_response :error
- $stderr.rewind && err = $stderr.read
- assert err =~ /Error occurred while parsing request parameters/
- ensure
- $stderr = STDERR
- end
+ output = StringIO.new
+ xml = "<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{ActiveSupport::Base64.encode64('ABC')}</avatar></pineapple>"
+ post "/parse", xml, default_headers.merge('action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => Logger.new(output))
+ assert_response :error
+ output.rewind && err = output.read
+ assert err =~ /Error occurred while parsing request parameters/
end
end
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index 9f4d6c530f..5875725b5d 100644
--- a/actionpack/test/dispatch/show_exceptions_test.rb
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -128,4 +128,11 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
get "/", {}, {'action_dispatch.show_exceptions' => true}
assert_equal "text/html; charset=utf-8", response.headers["Content-Type"]
end
+
+ test 'uses logger from env' do
+ @app = ProductionApp
+ output = StringIO.new
+ get "/", {}, {'action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => Logger.new(output)}
+ assert_match(/puke/, output.rewind && output.read)
+ end
end
diff --git a/actionpack/test/fixtures/test/_label_with_block.erb b/actionpack/test/fixtures/test/_label_with_block.erb
new file mode 100644
index 0000000000..40117e594e
--- /dev/null
+++ b/actionpack/test/fixtures/test/_label_with_block.erb
@@ -0,0 +1,4 @@
+<%= label 'post', 'message' do %>
+ Message
+ <%= text_field 'post', 'message' %>
+<% end %>
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index ccedcd7dac..0758106a40 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -230,6 +230,13 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal('<label for="post_title">The title, please:</label>', label(:post, :title) { "The title, please:" })
end
+ def test_label_with_block_in_erb
+ path = ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH)
+ view_paths = ActionView::PathSet.new([path])
+ view = ActionView::Base.new(view_paths)
+ assert_equal "<label for=\"post_message\">\n Message\n <input id=\"post_message\" name=\"post[message]\" size=\"30\" type=\"text\" />\n</label>", view.render("test/label_with_block")
+ end
+
def test_text_field
assert_dom_equal(
'<input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />', text_field("post", "title")
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 71a737caff..caea0b86bd 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,4 +1,14 @@
-* Added ActiveModel::Errors#added? to check if a specific error has been added *Martin Svalin*
+## Rails 3.2.0 (unreleased) ##
+
+* Renamed (with a deprecation the following constants):
+
+ ActiveModel::Serialization => ActiveModel::Serializable
+ ActiveModel::Serializers::JSON => ActiveModel::Serializable::JSON
+ ActiveModel::Serializers::Xml => ActiveModel::Serializable::XML
+
+ *José Valim*
+
+* Add ActiveModel::Errors#added? to check if a specific error has been added *Martin Svalin*
* Add ability to define strict validation(with :strict => true option) that always raises exception when fails *Bogdan Gusiev*
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index d0e2a6f39c..7ea04344f0 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -43,6 +43,7 @@ module ActiveModel
autoload :Observer, 'active_model/observing'
autoload :Observing
autoload :SecurePassword
+ autoload :Serializable
autoload :Serialization
autoload :TestCase
autoload :Translation
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index 37d0c9a0b9..0621a175bd 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -41,7 +41,7 @@ module ActiveModel
# You can choose not to have all three callbacks by passing a hash to the
# define_model_callbacks method.
#
- # define_model_callbacks :create, :only => :after, :before
+ # define_model_callbacks :create, :only => [:after, :before]
#
# Would only create the after_create and before_create callback methods in your
# class.
diff --git a/activemodel/lib/active_model/serializable.rb b/activemodel/lib/active_model/serializable.rb
new file mode 100644
index 0000000000..86770a25e4
--- /dev/null
+++ b/activemodel/lib/active_model/serializable.rb
@@ -0,0 +1,144 @@
+require 'active_support/core_ext/hash/except'
+require 'active_support/core_ext/hash/slice'
+require 'active_support/core_ext/array/wrap'
+require 'active_support/core_ext/string/inflections'
+
+module ActiveModel
+ # == Active Model Serializable
+ #
+ # Provides a basic serialization to a serializable_hash for your object.
+ #
+ # A minimal implementation could be:
+ #
+ # class Person
+ #
+ # include ActiveModel::Serializable
+ #
+ # attr_accessor :name
+ #
+ # def attributes
+ # {'name' => name}
+ # end
+ #
+ # end
+ #
+ # Which would provide you with:
+ #
+ # person = Person.new
+ # person.serializable_hash # => {"name"=>nil}
+ # person.name = "Bob"
+ # person.serializable_hash # => {"name"=>"Bob"}
+ #
+ # You need to declare some sort of attributes hash which contains the attributes
+ # you want to serialize and their current value.
+ #
+ # Most of the time though, you will want to include the JSON or XML
+ # serializations. Both of these modules automatically include the
+ # ActiveModel::Serialization module, so there is no need to explicitly
+ # include it.
+ #
+ # So a minimal implementation including XML and JSON would be:
+ #
+ # class Person
+ #
+ # include ActiveModel::Serializable::JSON
+ # include ActiveModel::Serializable::XML
+ #
+ # attr_accessor :name
+ #
+ # def attributes
+ # {'name' => name}
+ # end
+ #
+ # end
+ #
+ # Which would provide you with:
+ #
+ # person = Person.new
+ # person.serializable_hash # => {"name"=>nil}
+ # person.as_json # => {"name"=>nil}
+ # person.to_json # => "{\"name\":null}"
+ # person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
+ #
+ # person.name = "Bob"
+ # person.serializable_hash # => {"name"=>"Bob"}
+ # person.as_json # => {"name"=>"Bob"}
+ # person.to_json # => "{\"name\":\"Bob\"}"
+ # person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
+ #
+ # Valid options are <tt>:only</tt>, <tt>:except</tt> and <tt>:methods</tt> .
+ module Serializable
+ extend ActiveSupport::Concern
+
+ autoload :JSON, "active_model/serializable/json"
+ autoload :XML, "active_model/serializable/xml"
+
+ def serializable_hash(options = nil)
+ options ||= {}
+
+ attribute_names = attributes.keys.sort
+ if only = options[:only]
+ attribute_names &= Array.wrap(only).map(&:to_s)
+ elsif except = options[:except]
+ attribute_names -= Array.wrap(except).map(&:to_s)
+ end
+
+ hash = {}
+ attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
+
+ method_names = Array.wrap(options[:methods]).select { |n| respond_to?(n) }
+ method_names.each { |n| hash[n] = send(n) }
+
+ serializable_add_includes(options) do |association, records, opts|
+ hash[association] = if records.is_a?(Enumerable)
+ records.map { |a| a.serializable_hash(opts) }
+ else
+ records.serializable_hash(opts)
+ end
+ end
+
+ hash
+ end
+
+ private
+
+ # Hook method defining how an attribute value should be retrieved for
+ # serialization. By default this is assumed to be an instance named after
+ # the attribute. Override this method in subclasses should you need to
+ # retrieve the value for a given attribute differently:
+ #
+ # class MyClass
+ # include ActiveModel::Validations
+ #
+ # def initialize(data = {})
+ # @data = data
+ # end
+ #
+ # def read_attribute_for_serialization(key)
+ # @data[key]
+ # end
+ # end
+ #
+ alias :read_attribute_for_serialization :send
+
+ # Add associations specified via the <tt>:include</tt> option.
+ #
+ # Expects a block that takes as arguments:
+ # +association+ - name of the association
+ # +records+ - the association record(s) to be serialized
+ # +opts+ - options for the association records
+ def serializable_add_includes(options = {}) #:nodoc:
+ return unless include = options[:include]
+
+ unless include.is_a?(Hash)
+ include = Hash[Array.wrap(include).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }]
+ end
+
+ include.each do |association, opts|
+ if records = send(association)
+ yield association, records, opts
+ end
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/serializable/json.rb b/activemodel/lib/active_model/serializable/json.rb
new file mode 100644
index 0000000000..79173929e4
--- /dev/null
+++ b/activemodel/lib/active_model/serializable/json.rb
@@ -0,0 +1,108 @@
+require 'active_support/json'
+require 'active_support/core_ext/class/attribute'
+
+module ActiveModel
+ # == Active Model Serializable as JSON
+ module Serializable
+ module JSON
+ extend ActiveSupport::Concern
+ include ActiveModel::Serializable
+
+ included do
+ extend ActiveModel::Naming
+
+ class_attribute :include_root_in_json
+ self.include_root_in_json = true
+ end
+
+ # Returns a hash representing the model. Some configuration can be
+ # passed through +options+.
+ #
+ # The option <tt>include_root_in_json</tt> controls the top-level behavior
+ # of +as_json+. If true (the default) +as_json+ will emit a single root
+ # node named after the object's type. For example:
+ #
+ # user = User.find(1)
+ # user.as_json
+ # # => { "user": {"id": 1, "name": "Konata Izumi", "age": 16,
+ # "created_at": "2006/08/01", "awesome": true} }
+ #
+ # ActiveRecord::Base.include_root_in_json = false
+ # user.as_json
+ # # => {"id": 1, "name": "Konata Izumi", "age": 16,
+ # "created_at": "2006/08/01", "awesome": true}
+ #
+ # 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 Hash will include all the model's
+ # attributes. For example:
+ #
+ # user = User.find(1)
+ # user.as_json
+ # # => {"id": 1, "name": "Konata Izumi", "age": 16,
+ # "created_at": "2006/08/01", "awesome": true}
+ #
+ # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes
+ # included, and work similar to the +attributes+ method. For example:
+ #
+ # user.as_json(:only => [ :id, :name ])
+ # # => {"id": 1, "name": "Konata Izumi"}
+ #
+ # user.as_json(:except => [ :id, :created_at, :age ])
+ # # => {"name": "Konata Izumi", "awesome": true}
+ #
+ # To include the result of some method calls on the model use <tt>:methods</tt>:
+ #
+ # user.as_json(:methods => :permalink)
+ # # => {"id": 1, "name": "Konata Izumi", "age": 16,
+ # "created_at": "2006/08/01", "awesome": true,
+ # "permalink": "1-konata-izumi"}
+ #
+ # To include associations use <tt>:include</tt>:
+ #
+ # user.as_json(:include => :posts)
+ # # => {"id": 1, "name": "Konata Izumi", "age": 16,
+ # "created_at": "2006/08/01", "awesome": true,
+ # "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"},
+ # {"id": 2, author_id: 1, "title": "So I was thinking"}]}
+ #
+ # Second level and higher order associations work as well:
+ #
+ # user.as_json(:include => { :posts => {
+ # :include => { :comments => {
+ # :only => :body } },
+ # :only => :title } })
+ # # => {"id": 1, "name": "Konata Izumi", "age": 16,
+ # "created_at": "2006/08/01", "awesome": true,
+ # "posts": [{"comments": [{"body": "1st post!"}, {"body": "Second!"}],
+ # "title": "Welcome to the weblog"},
+ # {"comments": [{"body": "Don't think too hard"}],
+ # "title": "So I was thinking"}]}
+ def as_json(options = nil)
+ root = include_root_in_json
+ root = options[:root] if options.try(:key?, :root)
+ if root
+ root = self.class.model_name.element if root == true
+ { root => serializable_hash(options) }
+ else
+ serializable_hash(options)
+ end
+ end
+
+ def from_json(json, include_root=include_root_in_json)
+ hash = ActiveSupport::JSON.decode(json)
+ hash = hash.values.first if include_root
+ self.attributes = hash
+ self
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/serializable/xml.rb b/activemodel/lib/active_model/serializable/xml.rb
new file mode 100644
index 0000000000..d11cee9b42
--- /dev/null
+++ b/activemodel/lib/active_model/serializable/xml.rb
@@ -0,0 +1,195 @@
+require 'active_support/core_ext/array/wrap'
+require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/array/conversions'
+require 'active_support/core_ext/hash/conversions'
+require 'active_support/core_ext/hash/slice'
+
+module ActiveModel
+ # == Active Model Serializable as XML
+ module Serializable
+ module XML
+ extend ActiveSupport::Concern
+ include ActiveModel::Serializable
+
+ class Serializer #:nodoc:
+ class Attribute #:nodoc:
+ attr_reader :name, :value, :type
+
+ def initialize(name, serializable, value)
+ @name, @serializable = name, serializable
+ value = value.in_time_zone if value.respond_to?(:in_time_zone)
+ @value = value
+ @type = compute_type
+ end
+
+ def decorations
+ decorations = {}
+ decorations[:encoding] = 'base64' if type == :binary
+ decorations[:type] = (type == :string) ? nil : type
+ decorations[:nil] = true if value.nil?
+ decorations
+ end
+
+ protected
+
+ def compute_type
+ return if value.nil?
+ type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name]
+ type ||= :string if value.respond_to?(:to_str)
+ type ||= :yaml
+ type
+ end
+ end
+
+ class MethodAttribute < Attribute #:nodoc:
+ end
+
+ attr_reader :options
+
+ def initialize(serializable, options = nil)
+ @serializable = serializable
+ @options = options ? options.dup : {}
+ end
+
+ def serializable_hash
+ @serializable.serializable_hash(@options.except(:include))
+ end
+
+ def serializable_collection
+ methods = Array.wrap(options[:methods]).map(&:to_s)
+ serializable_hash.map do |name, value|
+ name = name.to_s
+ if methods.include?(name)
+ self.class::MethodAttribute.new(name, @serializable, value)
+ else
+ self.class::Attribute.new(name, @serializable, value)
+ end
+ end
+ end
+
+ def serialize
+ require 'builder' unless defined? ::Builder
+
+ options[:indent] ||= 2
+ options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
+
+ @builder = options[:builder]
+ @builder.instruct! unless options[:skip_instruct]
+
+ root = (options[:root] || @serializable.class.model_name.element).to_s
+ root = ActiveSupport::XmlMini.rename_key(root, options)
+
+ args = [root]
+ args << {:xmlns => options[:namespace]} if options[:namespace]
+ args << {:type => options[:type]} if options[:type] && !options[:skip_types]
+
+ @builder.tag!(*args) do
+ add_attributes_and_methods
+ add_includes
+ add_extra_behavior
+ add_procs
+ yield @builder if block_given?
+ end
+ end
+
+ private
+
+ def add_extra_behavior
+ end
+
+ def add_attributes_and_methods
+ serializable_collection.each do |attribute|
+ key = ActiveSupport::XmlMini.rename_key(attribute.name, options)
+ ActiveSupport::XmlMini.to_tag(key, attribute.value,
+ options.merge(attribute.decorations))
+ end
+ end
+
+ def add_includes
+ @serializable.send(:serializable_add_includes, options) do |association, records, opts|
+ add_associations(association, records, opts)
+ end
+ end
+
+ # TODO This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well.
+ def add_associations(association, records, opts)
+ merged_options = opts.merge(options.slice(:builder, :indent))
+ merged_options[:skip_instruct] = true
+
+ if records.is_a?(Enumerable)
+ tag = ActiveSupport::XmlMini.rename_key(association.to_s, options)
+ type = options[:skip_types] ? { } : {:type => "array"}
+ association_name = association.to_s.singularize
+ merged_options[:root] = association_name
+
+ if records.empty?
+ @builder.tag!(tag, type)
+ else
+ @builder.tag!(tag, type) do
+ records.each do |record|
+ if options[:skip_types]
+ record_type = {}
+ else
+ record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name
+ record_type = {:type => record_class}
+ end
+
+ record.to_xml merged_options.merge(record_type)
+ end
+ end
+ end
+ else
+ merged_options[:root] = association.to_s
+ records.to_xml(merged_options)
+ end
+ end
+
+ def add_procs
+ if procs = options.delete(:procs)
+ Array.wrap(procs).each do |proc|
+ if proc.arity == 1
+ proc.call(options)
+ else
+ proc.call(options, @serializable)
+ end
+ end
+ end
+ end
+ end
+
+ # Returns XML representing the model. Configuration can be
+ # passed through +options+.
+ #
+ # Without any +options+, the returned XML string will include all the model's
+ # attributes. For example:
+ #
+ # user = User.find(1)
+ # user.to_xml
+ #
+ # <?xml version="1.0" encoding="UTF-8"?>
+ # <user>
+ # <id type="integer">1</id>
+ # <name>David</name>
+ # <age type="integer">16</age>
+ # <created-at type="datetime">2011-01-30T22:29:23Z</created-at>
+ # </user>
+ #
+ # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes
+ # included, and work similar to the +attributes+ method.
+ #
+ # To include the result of some method calls on the model use <tt>:methods</tt>.
+ #
+ # To include associations use <tt>:include</tt>.
+ #
+ # For further documentation see activerecord/lib/active_record/serializers/xml_serializer.xml.
+ def to_xml(options = {}, &block)
+ Serializer.new(self, options).serialize(&block)
+ end
+
+ def from_xml(xml)
+ self.attributes = Hash.from_xml(xml).values.first
+ self
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb
index a4b58ab456..439302c632 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -1,139 +1,10 @@
-require 'active_support/core_ext/hash/except'
-require 'active_support/core_ext/hash/slice'
-require 'active_support/core_ext/array/wrap'
-
-
module ActiveModel
- # == Active Model Serialization
- #
- # Provides a basic serialization to a serializable_hash for your object.
- #
- # A minimal implementation could be:
- #
- # class Person
- #
- # include ActiveModel::Serialization
- #
- # attr_accessor :name
- #
- # def attributes
- # {'name' => name}
- # end
- #
- # end
- #
- # Which would provide you with:
- #
- # person = Person.new
- # person.serializable_hash # => {"name"=>nil}
- # person.name = "Bob"
- # person.serializable_hash # => {"name"=>"Bob"}
- #
- # You need to declare some sort of attributes hash which contains the attributes
- # you want to serialize and their current value.
- #
- # Most of the time though, you will want to include the JSON or XML
- # serializations. Both of these modules automatically include the
- # ActiveModel::Serialization module, so there is no need to explicitly
- # include it.
- #
- # So a minimal implementation including XML and JSON would be:
- #
- # class Person
- #
- # include ActiveModel::Serializers::JSON
- # include ActiveModel::Serializers::Xml
- #
- # attr_accessor :name
- #
- # def attributes
- # {'name' => name}
- # end
- #
- # end
- #
- # Which would provide you with:
- #
- # person = Person.new
- # person.serializable_hash # => {"name"=>nil}
- # person.as_json # => {"name"=>nil}
- # person.to_json # => "{\"name\":null}"
- # person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
- #
- # person.name = "Bob"
- # person.serializable_hash # => {"name"=>"Bob"}
- # person.as_json # => {"name"=>"Bob"}
- # person.to_json # => "{\"name\":\"Bob\"}"
- # person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
- #
- # Valid options are <tt>:only</tt>, <tt>:except</tt> and <tt>:methods</tt> .
module Serialization
- def serializable_hash(options = nil)
- options ||= {}
-
- attribute_names = attributes.keys.sort
- if only = options[:only]
- attribute_names &= Array.wrap(only).map(&:to_s)
- elsif except = options[:except]
- attribute_names -= Array.wrap(except).map(&:to_s)
- end
-
- hash = {}
- attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
-
- method_names = Array.wrap(options[:methods]).select { |n| respond_to?(n) }
- method_names.each { |n| hash[n] = send(n) }
-
- serializable_add_includes(options) do |association, records, opts|
- hash[association] = if records.is_a?(Enumerable)
- records.map { |a| a.serializable_hash(opts) }
- else
- records.serializable_hash(opts)
- end
- end
+ extend ActiveSupport::Concern
+ include ActiveModel::Serializable
- hash
+ included do
+ ActiveSupport::Deprecation.warn "ActiveModel::Serialization is deprecated in favor of ActiveModel::Serializable"
end
-
- private
-
- # Hook method defining how an attribute value should be retrieved for
- # serialization. By default this is assumed to be an instance named after
- # the attribute. Override this method in subclasses should you need to
- # retrieve the value for a given attribute differently:
- #
- # class MyClass
- # include ActiveModel::Validations
- #
- # def initialize(data = {})
- # @data = data
- # end
- #
- # def read_attribute_for_serialization(key)
- # @data[key]
- # end
- # end
- #
- alias :read_attribute_for_serialization :send
-
- # Add associations specified via the <tt>:include</tt> option.
- #
- # Expects a block that takes as arguments:
- # +association+ - name of the association
- # +records+ - the association record(s) to be serialized
- # +opts+ - options for the association records
- def serializable_add_includes(options = {}) #:nodoc:
- return unless include = options[:include]
-
- unless include.is_a?(Hash)
- include = Hash[Array.wrap(include).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }]
- end
-
- include.each do |association, opts|
- if records = send(association)
- yield association, records, opts
- end
- end
- end
end
-end
+end \ No newline at end of file
diff --git a/activemodel/lib/active_model/serializer.rb b/activemodel/lib/active_model/serializer.rb
new file mode 100644
index 0000000000..0e23df2f2b
--- /dev/null
+++ b/activemodel/lib/active_model/serializer.rb
@@ -0,0 +1,253 @@
+require "active_support/core_ext/class/attribute"
+require "active_support/core_ext/string/inflections"
+require "active_support/core_ext/module/anonymous"
+require "set"
+
+module ActiveModel
+ # Active Model Array Serializer
+ #
+ # It serializes an array checking if each element that implements
+ # the +active_model_serializer+ method passing down the current scope.
+ class ArraySerializer
+ attr_reader :object, :scope
+
+ def initialize(object, scope)
+ @object, @scope = object, scope
+ end
+
+ def serializable_array
+ @object.map do |item|
+ if item.respond_to?(:active_model_serializer) && (serializer = item.active_model_serializer)
+ serializer.new(item, scope)
+ else
+ item
+ end
+ end
+ end
+
+ def as_json(*args)
+ serializable_array.as_json(*args)
+ end
+ end
+
+ # Active Model Serializer
+ #
+ # Provides a basic serializer implementation that allows you to easily
+ # control how a given object is going to be serialized. On initialization,
+ # it expects to object as arguments, a resource and a scope. For example,
+ # one may do in a controller:
+ #
+ # PostSerializer.new(@post, current_user).to_json
+ #
+ # The object to be serialized is the +@post+ and the scope is +current_user+.
+ #
+ # We use the scope to check if a given attribute should be serialized or not.
+ # For example, some attributes maybe only be returned if +current_user+ is the
+ # author of the post:
+ #
+ # class PostSerializer < ActiveModel::Serializer
+ # attributes :title, :body
+ # has_many :comments
+ #
+ # private
+ #
+ # def attributes
+ # hash = super
+ # hash.merge!(:email => post.email) if author?
+ # hash
+ # end
+ #
+ # def author?
+ # post.author == scope
+ # end
+ # end
+ #
+ class Serializer
+ module Associations #:nodoc:
+ class Config < Struct.new(:name, :options) #:nodoc:
+ def serializer
+ options[:serializer]
+ end
+ end
+
+ class HasMany < Config #:nodoc:
+ def serialize(collection, scope)
+ collection.map do |item|
+ serializer.new(item, scope).serializable_hash
+ end
+ end
+
+ def serialize_ids(collection, scope)
+ # use named scopes if they are present
+ # return collection.ids if collection.respond_to?(:ids)
+
+ collection.map do |item|
+ item.read_attribute_for_serialization(:id)
+ end
+ end
+ end
+
+ class HasOne < Config #:nodoc:
+ def serialize(object, scope)
+ object && serializer.new(object, scope).serializable_hash
+ end
+
+ def serialize_ids(object, scope)
+ object && object.read_attribute_for_serialization(:id)
+ end
+ end
+ end
+
+ class_attribute :_attributes
+ self._attributes = Set.new
+
+ class_attribute :_associations
+ self._associations = []
+
+ class_attribute :_root
+ class_attribute :_embed
+ self._embed = :objects
+ class_attribute :_root_embed
+
+ class << self
+ # Define attributes to be used in the serialization.
+ def attributes(*attrs)
+ self._attributes += attrs
+ end
+
+ def associate(klass, attrs) #:nodoc:
+ options = attrs.extract_options!
+ self._associations += attrs.map do |attr|
+ unless method_defined?(attr)
+ class_eval "def #{attr}() object.#{attr} end", __FILE__, __LINE__
+ end
+
+ options[:serializer] ||= const_get("#{attr.to_s.camelize}Serializer")
+ klass.new(attr, options)
+ end
+ end
+
+ # Defines an association in the object should be rendered.
+ #
+ # The serializer object should implement the association name
+ # as a method which should return an array when invoked. If a method
+ # with the association name does not exist, the association name is
+ # dispatched to the serialized object.
+ def has_many(*attrs)
+ associate(Associations::HasMany, attrs)
+ end
+
+ # Defines an association in the object should be rendered.
+ #
+ # The serializer object should implement the association name
+ # as a method which should return an object when invoked. If a method
+ # with the association name does not exist, the association name is
+ # dispatched to the serialized object.
+ def has_one(*attrs)
+ associate(Associations::HasOne, attrs)
+ end
+
+ # Define how associations should be embedded.
+ #
+ # embed :objects # Embed associations as full objects
+ # embed :ids # Embed only the association ids
+ # embed :ids, :include => true # Embed the association ids and include objects in the root
+ #
+ def embed(type, options={})
+ self._embed = type
+ self._root_embed = true if options[:include]
+ end
+
+ # Defines the root used on serialization. If false, disables the root.
+ def root(name)
+ self._root = name
+ end
+
+ def inherited(klass) #:nodoc:
+ return if klass.anonymous?
+
+ name = klass.name.demodulize.underscore.sub(/_serializer$/, '')
+
+ klass.class_eval do
+ alias_method name.to_sym, :object
+ root name.to_sym unless self._root == false
+ end
+ end
+ end
+
+ attr_reader :object, :scope
+
+ def initialize(object, scope)
+ @object, @scope = object, scope
+ end
+
+ # Returns a json representation of the serializable
+ # object including the root.
+ def as_json(*)
+ if _root
+ hash = { _root => serializable_hash }
+ hash.merge!(associations) if _root_embed
+ hash
+ else
+ serializable_hash
+ end
+ end
+
+ # Returns a hash representation of the serializable
+ # object without the root.
+ def serializable_hash
+ if _embed == :ids
+ attributes.merge(association_ids)
+ elsif _embed == :objects
+ attributes.merge(associations)
+ else
+ attributes
+ end
+ end
+
+ # Returns a hash representation of the serializable
+ # object associations.
+ def associations
+ hash = {}
+
+ _associations.each do |association|
+ associated_object = send(association.name)
+ hash[association.name] = association.serialize(associated_object, scope)
+ end
+
+ hash
+ end
+
+ # Returns a hash representation of the serializable
+ # object associations ids.
+ def association_ids
+ hash = {}
+
+ _associations.each do |association|
+ associated_object = send(association.name)
+ hash[association.name] = association.serialize_ids(associated_object, scope)
+ end
+
+ hash
+ end
+
+ # Returns a hash representation of the serializable
+ # object attributes.
+ def attributes
+ hash = {}
+
+ _attributes.each do |name|
+ hash[name] = @object.read_attribute_for_serialization(name)
+ end
+
+ hash
+ end
+ end
+end
+
+class Array
+ # Array uses ActiveModel::ArraySerializer.
+ def active_model_serializer
+ ActiveModel::ArraySerializer
+ end
+end \ No newline at end of file
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index c845440120..9efd7c5f69 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -1,108 +1,12 @@
-require 'active_support/json'
-require 'active_support/core_ext/class/attribute'
-
module ActiveModel
- # == Active Model JSON Serializer
module Serializers
module JSON
extend ActiveSupport::Concern
- include ActiveModel::Serialization
+ include ActiveModel::Serializable::JSON
included do
- extend ActiveModel::Naming
-
- class_attribute :include_root_in_json
- self.include_root_in_json = true
- end
-
- # Returns a hash representing the model. Some configuration can be
- # passed through +options+.
- #
- # The option <tt>include_root_in_json</tt> controls the top-level behavior
- # of +as_json+. If true (the default) +as_json+ will emit a single root
- # node named after the object's type. For example:
- #
- # user = User.find(1)
- # user.as_json
- # # => { "user": {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true} }
- #
- # ActiveRecord::Base.include_root_in_json = false
- # user.as_json
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true}
- #
- # 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 Hash will include all the model's
- # attributes. For example:
- #
- # user = User.find(1)
- # user.as_json
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true}
- #
- # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes
- # included, and work similar to the +attributes+ method. For example:
- #
- # user.as_json(:only => [ :id, :name ])
- # # => {"id": 1, "name": "Konata Izumi"}
- #
- # user.as_json(:except => [ :id, :created_at, :age ])
- # # => {"name": "Konata Izumi", "awesome": true}
- #
- # To include the result of some method calls on the model use <tt>:methods</tt>:
- #
- # user.as_json(:methods => :permalink)
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true,
- # "permalink": "1-konata-izumi"}
- #
- # To include associations use <tt>:include</tt>:
- #
- # user.as_json(:include => :posts)
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true,
- # "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"},
- # {"id": 2, author_id: 1, "title": "So I was thinking"}]}
- #
- # Second level and higher order associations work as well:
- #
- # user.as_json(:include => { :posts => {
- # :include => { :comments => {
- # :only => :body } },
- # :only => :title } })
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true,
- # "posts": [{"comments": [{"body": "1st post!"}, {"body": "Second!"}],
- # "title": "Welcome to the weblog"},
- # {"comments": [{"body": "Don't think too hard"}],
- # "title": "So I was thinking"}]}
- def as_json(options = nil)
- root = include_root_in_json
- root = options[:root] if options.try(:key?, :root)
- if root
- root = self.class.model_name.element if root == true
- { root => serializable_hash(options) }
- else
- serializable_hash(options)
- end
- end
-
- def from_json(json, include_root=include_root_in_json)
- hash = ActiveSupport::JSON.decode(json)
- hash = hash.values.first if include_root
- self.attributes = hash
- self
+ ActiveSupport::Deprecation.warn "ActiveModel::Serializers::JSON is deprecated in favor of ActiveModel::Serializable::JSON"
end
end
end
-end
+end \ No newline at end of file
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index d61d9d7119..620390da6b 100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -1,195 +1,14 @@
-require 'active_support/core_ext/array/wrap'
-require 'active_support/core_ext/class/attribute_accessors'
-require 'active_support/core_ext/array/conversions'
-require 'active_support/core_ext/hash/conversions'
-require 'active_support/core_ext/hash/slice'
-
module ActiveModel
- # == Active Model XML Serializer
module Serializers
module Xml
extend ActiveSupport::Concern
- include ActiveModel::Serialization
-
- class Serializer #:nodoc:
- class Attribute #:nodoc:
- attr_reader :name, :value, :type
-
- def initialize(name, serializable, value)
- @name, @serializable = name, serializable
- value = value.in_time_zone if value.respond_to?(:in_time_zone)
- @value = value
- @type = compute_type
- end
-
- def decorations
- decorations = {}
- decorations[:encoding] = 'base64' if type == :binary
- decorations[:type] = (type == :string) ? nil : type
- decorations[:nil] = true if value.nil?
- decorations
- end
-
- protected
-
- def compute_type
- return if value.nil?
- type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name]
- type ||= :string if value.respond_to?(:to_str)
- type ||= :yaml
- type
- end
- end
-
- class MethodAttribute < Attribute #:nodoc:
- end
-
- attr_reader :options
-
- def initialize(serializable, options = nil)
- @serializable = serializable
- @options = options ? options.dup : {}
- end
-
- def serializable_hash
- @serializable.serializable_hash(@options.except(:include))
- end
-
- def serializable_collection
- methods = Array.wrap(options[:methods]).map(&:to_s)
- serializable_hash.map do |name, value|
- name = name.to_s
- if methods.include?(name)
- self.class::MethodAttribute.new(name, @serializable, value)
- else
- self.class::Attribute.new(name, @serializable, value)
- end
- end
- end
-
- def serialize
- require 'builder' unless defined? ::Builder
-
- options[:indent] ||= 2
- options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
-
- @builder = options[:builder]
- @builder.instruct! unless options[:skip_instruct]
+ include ActiveModel::Serializable::XML
- root = (options[:root] || @serializable.class.model_name.element).to_s
- root = ActiveSupport::XmlMini.rename_key(root, options)
-
- args = [root]
- args << {:xmlns => options[:namespace]} if options[:namespace]
- args << {:type => options[:type]} if options[:type] && !options[:skip_types]
-
- @builder.tag!(*args) do
- add_attributes_and_methods
- add_includes
- add_extra_behavior
- add_procs
- yield @builder if block_given?
- end
- end
-
- private
-
- def add_extra_behavior
- end
-
- def add_attributes_and_methods
- serializable_collection.each do |attribute|
- key = ActiveSupport::XmlMini.rename_key(attribute.name, options)
- ActiveSupport::XmlMini.to_tag(key, attribute.value,
- options.merge(attribute.decorations))
- end
- end
-
- def add_includes
- @serializable.send(:serializable_add_includes, options) do |association, records, opts|
- add_associations(association, records, opts)
- end
- end
-
- # TODO This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well.
- def add_associations(association, records, opts)
- merged_options = opts.merge(options.slice(:builder, :indent))
- merged_options[:skip_instruct] = true
-
- if records.is_a?(Enumerable)
- tag = ActiveSupport::XmlMini.rename_key(association.to_s, options)
- type = options[:skip_types] ? { } : {:type => "array"}
- association_name = association.to_s.singularize
- merged_options[:root] = association_name
-
- if records.empty?
- @builder.tag!(tag, type)
- else
- @builder.tag!(tag, type) do
- records.each do |record|
- if options[:skip_types]
- record_type = {}
- else
- record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name
- record_type = {:type => record_class}
- end
-
- record.to_xml merged_options.merge(record_type)
- end
- end
- end
- else
- merged_options[:root] = association.to_s
- records.to_xml(merged_options)
- end
- end
-
- def add_procs
- if procs = options.delete(:procs)
- Array.wrap(procs).each do |proc|
- if proc.arity == 1
- proc.call(options)
- else
- proc.call(options, @serializable)
- end
- end
- end
- end
- end
-
- # Returns XML representing the model. Configuration can be
- # passed through +options+.
- #
- # Without any +options+, the returned XML string will include all the model's
- # attributes. For example:
- #
- # user = User.find(1)
- # user.to_xml
- #
- # <?xml version="1.0" encoding="UTF-8"?>
- # <user>
- # <id type="integer">1</id>
- # <name>David</name>
- # <age type="integer">16</age>
- # <created-at type="datetime">2011-01-30T22:29:23Z</created-at>
- # </user>
- #
- # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes
- # included, and work similar to the +attributes+ method.
- #
- # To include the result of some method calls on the model use <tt>:methods</tt>.
- #
- # To include associations use <tt>:include</tt>.
- #
- # For further documentation see activerecord/lib/active_record/serializers/xml_serializer.xml.
- def to_xml(options = {}, &block)
- Serializer.new(self, options).serialize(&block)
- end
+ Serializer = ActiveModel::Serializable::XML::Serializer
- def from_xml(xml)
- self.attributes = Hash.from_xml(xml).values.first
- self
+ included do
+ ActiveSupport::Deprecation.warn "ActiveModel::Serializers::Xml is deprecated in favor of ActiveModel::Serializable::XML"
end
end
end
-end
+end \ No newline at end of file
diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb
index 6f9004da7f..e8db73ba52 100644
--- a/activemodel/test/cases/naming_test.rb
+++ b/activemodel/test/cases/naming_test.rb
@@ -251,7 +251,7 @@ end
class NameWithAnonymousClassTest < Test::Unit::TestCase
def test_anonymous_class_without_name_argument
assert_raises(ArgumentError) do
- model_name = ActiveModel::Name.new(Class.new)
+ ActiveModel::Name.new(Class.new)
end
end
diff --git a/activemodel/test/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializable/json_test.rb
index a754d610b9..ad5b04091e 100644
--- a/activemodel/test/cases/serializers/json_serialization_test.rb
+++ b/activemodel/test/cases/serializable/json_test.rb
@@ -5,7 +5,7 @@ require 'active_support/core_ext/object/instance_variables'
class Contact
extend ActiveModel::Naming
- include ActiveModel::Serializers::JSON
+ include ActiveModel::Serializable::JSON
include ActiveModel::Validations
def attributes=(hash)
diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializable/xml_test.rb
index fc73d9dcd8..817ca1e736 100644
--- a/activemodel/test/cases/serializers/xml_serialization_test.rb
+++ b/activemodel/test/cases/serializable/xml_test.rb
@@ -5,7 +5,7 @@ require 'ostruct'
class Contact
extend ActiveModel::Naming
- include ActiveModel::Serializers::Xml
+ include ActiveModel::Serializable::XML
attr_accessor :address, :friends
@@ -24,7 +24,7 @@ end
class Address
extend ActiveModel::Naming
- include ActiveModel::Serializers::Xml
+ include ActiveModel::Serializable::XML
attr_accessor :street, :city, :state, :zip
diff --git a/activemodel/test/cases/serialization_test.rb b/activemodel/test/cases/serializable_test.rb
index b8dad9d51f..46ee372c6f 100644
--- a/activemodel/test/cases/serialization_test.rb
+++ b/activemodel/test/cases/serializable_test.rb
@@ -3,7 +3,7 @@ require 'active_support/core_ext/object/instance_variables'
class SerializationTest < ActiveModel::TestCase
class User
- include ActiveModel::Serialization
+ include ActiveModel::Serializable
attr_accessor :name, :email, :gender, :address, :friends
@@ -22,7 +22,7 @@ class SerializationTest < ActiveModel::TestCase
end
class Address
- include ActiveModel::Serialization
+ include ActiveModel::Serializable
attr_accessor :street, :city, :state, :zip
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 2f01fbb829..852debcdde 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -1239,7 +1239,7 @@ module ActiveRecord
if match_data
rest = name[match_data[0].length, name.length]
rest = rest[1, rest.length] if rest.start_with? "."
- [match_data[1], (rest.length > 0 ? rest : nil)]
+ [match_data[1], (rest.any? ? rest : nil)]
end
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index f0891440a6..0c32ad5139 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/module/delegation'
@@ -155,8 +156,8 @@ module ActiveRecord
end
queries.map do |sql|
- @klass.connection.explain(sql)
- end.join
+ "EXPLAIN for: #{sql}\n#{@klass.connection.explain(sql)}"
+ end.join("\n")
end
def to_a
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
index 5ad40d8cd9..c23514c465 100644
--- a/activerecord/lib/active_record/serialization.rb
+++ b/activerecord/lib/active_record/serialization.rb
@@ -2,7 +2,7 @@ module ActiveRecord #:nodoc:
# = Active Record Serialization
module Serialization
extend ActiveSupport::Concern
- include ActiveModel::Serializers::JSON
+ include ActiveModel::Serializable::JSON
def serializable_hash(options = nil)
options = options.try(:clone) || {}
diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
index 0e7f57aa43..2da836ef0c 100644
--- a/activerecord/lib/active_record/serializers/xml_serializer.rb
+++ b/activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -3,7 +3,7 @@ require 'active_support/core_ext/hash/conversions'
module ActiveRecord #:nodoc:
module Serialization
- include ActiveModel::Serializers::Xml
+ include ActiveModel::Serializable::XML
# Builds an XML document to represent the model. Some configuration is
# available through +options+. However more complicated cases should
@@ -176,13 +176,13 @@ module ActiveRecord #:nodoc:
end
end
- class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc:
+ class XmlSerializer < ActiveModel::Serializable::XML::Serializer #:nodoc:
def initialize(*args)
super
options[:except] = Array.wrap(options[:except]) | Array.wrap(@serializable.class.inheritance_column)
end
- class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
+ class Attribute < ActiveModel::Serializable::XML::Serializer::Attribute #:nodoc:
def compute_type
klass = @serializable.class
type = if klass.serialized_attributes.key?(name)
diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb
index 8ea777b72b..68ed361aeb 100644
--- a/activerecord/test/cases/adapters/mysql2/explain_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb
@@ -9,12 +9,15 @@ module ActiveRecord
def test_explain_for_one_query
explain = Developer.where(:id => 1).explain
+ assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
assert_match %(developers | const), explain
end
def test_explain_with_eager_loading
explain = Developer.where(:id => 1).includes(:audit_logs).explain
+ assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
assert_match %(developers | const), explain
+ assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` IN (1)), explain
assert_match %(audit_logs | ALL), explain
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb
index 0d599ed37f..0b61f61572 100644
--- a/activerecord/test/cases/adapters/postgresql/explain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb
@@ -9,6 +9,7 @@ module ActiveRecord
def test_explain_for_one_query
explain = Developer.where(:id => 1).explain
+ assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain
assert_match %(QUERY PLAN), explain
assert_match %(Index Scan using developers_pkey on developers), explain
end
@@ -16,7 +17,9 @@ module ActiveRecord
def test_explain_with_eager_loading
explain = Developer.where(:id => 1).includes(:audit_logs).explain
assert_match %(QUERY PLAN), explain
+ assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain
assert_match %(Index Scan using developers_pkey on developers), explain
+ assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain
assert_match %(Seq Scan on audit_logs), explain
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
index e18892821d..b227bce680 100644
--- a/activerecord/test/cases/adapters/sqlite3/explain_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
@@ -9,12 +9,15 @@ module ActiveRecord
def test_explain_for_one_query
explain = Developer.where(:id => 1).explain
+ assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain
assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
end
def test_explain_with_eager_loading
explain = Developer.where(:id => 1).includes(:audit_logs).explain
+ assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain
assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
+ assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain
assert_match(/(SCAN )?TABLE audit_logs/, explain)
end
end
diff --git a/activeresource/lib/active_resource/custom_methods.rb b/activeresource/lib/active_resource/custom_methods.rb
index c1931b2758..f7cb381711 100644
--- a/activeresource/lib/active_resource/custom_methods.rb
+++ b/activeresource/lib/active_resource/custom_methods.rb
@@ -23,7 +23,7 @@ module ActiveResource
# self.site = "http://37s.sunrise.i:3000"
# end
#
- # Person.new(:name => 'Ryan).post(:register) # POST /people/new/register.json
+ # Person.new(:name => 'Ryan').post(:register) # POST /people/new/register.json
# # => { :id => 1, :name => 'Ryan' }
#
# Person.find(1).put(:promote, :position => 'Manager') # PUT /people/1/promote.json
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 18a115b369..465b0c1e16 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,5 +1,8 @@
## Rails 3.2.0 (unreleased) ##
+* (Date|DateTime|Time)#beginning_of_week accept an optional argument to
+ be able to set the day at which weeks are assumed to start.
+
* Deprecated ActiveSupport::MessageEncryptor#encrypt and decrypt. *José Valim*
* ActiveSupport::Notifications.subscribed provides subscriptions to events while a block runs. *fxn*
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index c6d5f29690..f0f67765c6 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -174,25 +174,48 @@ class Date
months_since(1)
end unless method_defined?(:next_month)
- # Returns a new Date/DateTime representing the "start" of this week (i.e, Monday; DateTime objects will have time set to 0:00).
- def beginning_of_week
- days_to_monday = self.wday!=0 ? self.wday-1 : 6
- result = self - days_to_monday
- self.acts_like?(:time) ? result.midnight : result
+ # Returns number of days to start of this week. Week is assumed to start on
+ # +start_day+, default is +:monday+.
+ def days_to_week_start(start_day = :monday)
+ start_day_number = DAYS_INTO_WEEK[start_day]
+ current_day_number = wday != 0 ? wday - 1 : 6
+ (current_day_number - start_day_number) % 7
+ end
+
+ # Returns a new +Date+/+DateTime+ representing the start of this week. Week is
+ # assumed to start on +start_day+, default is +:monday+. +DateTime+ objects
+ # have their time set to 0:00.
+ def beginning_of_week(start_day = :monday)
+ days_to_start = days_to_week_start(start_day)
+ result = self - days_to_start
+ acts_like?(:time) ? result.midnight : result
end
- alias :monday :beginning_of_week
alias :at_beginning_of_week :beginning_of_week
- # Returns a new Date/DateTime representing the end of this week (Sunday, DateTime objects will have time set to 23:59:59).
- def end_of_week
- days_to_sunday = self.wday!=0 ? 7-self.wday : 0
- result = self + days_to_sunday.days
+ # Returns a new +Date+/+DateTime+ representing the start of this week. Week is
+ # assumed to start on a Monday. +DateTime+ objects have their time set to 0:00.
+ def monday
+ beginning_of_week
+ end
+
+ # Returns a new +Date+/+DateTime+ representing the end of this week. Week is
+ # assumed to start on +start_day+, default is +:monday+. +DateTime+ objects
+ # have their time set to 23:59:59.
+ def end_of_week(start_day = :monday)
+ days_to_end = 6 - days_to_week_start(start_day)
+ result = self + days_to_end.days
self.acts_like?(:time) ? result.end_of_day : result
end
- alias :sunday :end_of_week
alias :at_end_of_week :end_of_week
- # Returns a new Date/DateTime representing the start of the given day in the previous week (default is :monday).
+ # Returns a new +Date+/+DateTime+ representing the end of this week. Week is
+ # assumed to start on a Monday. +DateTime+ objects have their time set to 23:59:59.
+ def sunday
+ end_of_week
+ end
+
+ # Returns a new +Date+/+DateTime+ representing the given +day+ in the previous
+ # week. Default is +:monday+. +DateTime+ objects have their time set to 0:00.
def prev_week(day = :monday)
result = (self - 7).beginning_of_week + DAYS_INTO_WEEK[day]
self.acts_like?(:time) ? result.change(:hour => 0) : result
diff --git a/activesupport/lib/active_support/core_ext/object/inclusion.rb b/activesupport/lib/active_support/core_ext/object/inclusion.rb
index b5671f66d0..f611cdd606 100644
--- a/activesupport/lib/active_support/core_ext/object/inclusion.rb
+++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb
@@ -1,15 +1,25 @@
class Object
- # Returns true if this object is included in the argument. Argument must be
- # any object which responds to +#include?+. Usage:
+ # Returns true if this object is included in the argument(s). Argument must be
+ # any object which responds to +#include?+ or optionally, multiple arguments can be passed in. Usage:
#
# characters = ["Konata", "Kagami", "Tsukasa"]
# "Konata".in?(characters) # => true
+ #
+ # character = "Konata"
+ # character.in?("Konata", "Kagami", "Tsukasa") # => true
#
- # This will throw an ArgumentError if the argument doesn't respond
+ # This will throw an ArgumentError if a single argument is passed in and it doesn't respond
# to +#include?+.
- def in?(another_object)
- another_object.include?(self)
- rescue NoMethodError
- raise ArgumentError.new("The parameter passed to #in? must respond to #include?")
+ def in?(*args)
+ if args.length > 1
+ args.include? self
+ else
+ another_object = args.first
+ if another_object.respond_to? :include?
+ another_object.include? self
+ else
+ raise ArgumentError.new("The single parameter passed to #in? must respond to #include?")
+ end
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/to_json.rb b/activesupport/lib/active_support/core_ext/object/to_json.rb
index 14ef27340e..e7dc60a612 100644
--- a/activesupport/lib/active_support/core_ext/object/to_json.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_json.rb
@@ -10,10 +10,10 @@ end
# several cases (for instance, the JSON implementation for Hash does not work) with inheritance
# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json.
[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass|
- klass.class_eval <<-RUBY, __FILE__, __LINE__
+ klass.class_eval do
# Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info.
def to_json(options = nil)
ActiveSupport::JSON.encode(self, options)
end
- RUBY
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 1e15529569..f3235d11bb 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -161,21 +161,40 @@ class Time
months_since(1)
end
- # Returns a new Time representing the "start" of this week (Monday, 0:00)
- def beginning_of_week
- days_to_monday = wday!=0 ? wday-1 : 6
- (self - days_to_monday.days).midnight
+ # Returns number of days to start of this week, week starts on start_day (default is :monday).
+ def days_to_week_start(start_day = :monday)
+ start_day_number = DAYS_INTO_WEEK[start_day]
+ current_day_number = wday != 0 ? wday - 1 : 6
+ days_span = current_day_number - start_day_number
+ days_span >= 0 ? days_span : 7 + days_span
+ end
+
+ # Returns a new Time representing the "start" of this week, week starts on start_day (default is :monday, i.e. Monday, 0:00).
+ def beginning_of_week(start_day = :monday)
+ days_to_start = days_to_week_start(start_day)
+ (self - days_to_start.days).midnight
end
- alias :monday :beginning_of_week
alias :at_beginning_of_week :beginning_of_week
- # Returns a new Time representing the end of this week, (end of Sunday)
- def end_of_week
- days_to_sunday = wday!=0 ? 7-wday : 0
- (self + days_to_sunday.days).end_of_day
+ # Returns a new +Date+/+DateTime+ representing the start of this week. Week is
+ # assumed to start on a Monday. +DateTime+ objects have their time set to 0:00.
+ def monday
+ beginning_of_week
+ end
+
+ # Returns a new Time representing the end of this week, week starts on start_day (default is :monday, i.e. end of Sunday).
+ def end_of_week(start_day = :monday)
+ days_to_end = 6 - days_to_week_start(start_day)
+ (self + days_to_end.days).end_of_day
end
alias :at_end_of_week :end_of_week
+ # Returns a new +Date+/+DateTime+ representing the end of this week. Week is
+ # assumed to start on a Monday. +DateTime+ objects have their time set to 23:59:59.
+ def sunday
+ end_of_week
+ end
+
# Returns a new Time representing the start of the given day in the previous week (default is :monday).
def prev_week(day = :monday)
ago(1.week).beginning_of_week.since(DAYS_INTO_WEEK[day].day).change(:hour => 0)
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index db90de4682..1372e71a61 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -527,7 +527,7 @@ module ActiveSupport #:nodoc:
class ClassCache
def initialize
- @store = Hash.new { |h, k| h[k] = Inflector.constantize(k) }
+ @store = Hash.new
end
def empty?
@@ -538,23 +538,24 @@ module ActiveSupport #:nodoc:
@store.key?(key)
end
- def []=(key, value)
- return unless key.respond_to?(:name)
-
- raise(ArgumentError, 'anonymous classes cannot be cached') if key.name.blank?
-
- @store[key.name] = value
+ def get(key)
+ key = key.name if key.respond_to?(:name)
+ @store[key] ||= Inflector.constantize(key)
end
+ alias :[] :get
- def [](key)
+ def safe_get(key)
key = key.name if key.respond_to?(:name)
-
- @store[key]
+ @store[key] || begin
+ klass = Inflector.safe_constantize(key)
+ @store[key] = klass
+ end
end
- alias :get :[]
- def store(name)
- self[name] = name
+ def store(klass)
+ return self unless klass.respond_to?(:name)
+ raise(ArgumentError, 'anonymous classes cannot be cached') if klass.name.empty?
+ @store[klass.name] = klass
self
end
@@ -571,10 +572,17 @@ module ActiveSupport #:nodoc:
end
# Get the reference for class named +name+.
+ # Raises an exception if referenced class does not exist.
def constantize(name)
Reference.get(name)
end
+ # Get the reference for class named +name+ if one exists.
+ # Otherwise returns nil.
+ def safe_constantize(name)
+ Reference.safe_get(name)
+ end
+
# Determine if the given constant has been automatically loaded.
def autoloaded?(desc)
# No name => anonymous module.
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index 469ae69258..b0fd3a3c0e 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -50,9 +50,9 @@ module ActiveSupport
end
# like encode, but only calls as_json, without encoding to string
- def as_json(value)
+ def as_json(value, use_options = true)
check_for_circular_references(value) do
- value.as_json(options_for(value))
+ use_options ? value.as_json(options_for(value)) : value.as_json
end
end
@@ -212,7 +212,7 @@ class Array
def as_json(options = nil) #:nodoc:
# use encoder as a proxy to call as_json on all elements, to protect from circular references
encoder = options && options[:encoder] || ActiveSupport::JSON::Encoding::Encoder.new(options)
- map { |v| encoder.as_json(v) }
+ map { |v| encoder.as_json(v, options) }
end
def encode_json(encoder) #:nodoc:
@@ -239,7 +239,7 @@ class Hash
# use encoder as a proxy to call as_json on all values in the subset, to protect from circular references
encoder = options && options[:encoder] || ActiveSupport::JSON::Encoding::Encoder.new(options)
result = self.is_a?(ActiveSupport::OrderedHash) ? ActiveSupport::OrderedHash : Hash
- result[subset.map { |k, v| [k.to_s, encoder.as_json(v)] }]
+ result[subset.map { |k, v| [k.to_s, encoder.as_json(v, options)] }]
end
def encode_json(encoder)
diff --git a/activesupport/test/class_cache_test.rb b/activesupport/test/class_cache_test.rb
index 752c0ee478..b96f476ce6 100644
--- a/activesupport/test/class_cache_test.rb
+++ b/activesupport/test/class_cache_test.rb
@@ -10,45 +10,58 @@ module ActiveSupport
def test_empty?
assert @cache.empty?
- @cache[ClassCacheTest] = ClassCacheTest
+ @cache.store(ClassCacheTest)
assert !@cache.empty?
end
def test_clear!
assert @cache.empty?
- @cache[ClassCacheTest] = ClassCacheTest
+ @cache.store(ClassCacheTest)
assert !@cache.empty?
@cache.clear!
assert @cache.empty?
end
def test_set_key
- @cache[ClassCacheTest] = ClassCacheTest
+ @cache.store(ClassCacheTest)
assert @cache.key?(ClassCacheTest.name)
end
- def test_set_rejects_strings
- @cache[ClassCacheTest.name] = ClassCacheTest
- assert @cache.empty?
- end
-
def test_get_with_class
- @cache[ClassCacheTest] = ClassCacheTest
- assert_equal ClassCacheTest, @cache[ClassCacheTest]
+ @cache.store(ClassCacheTest)
+ assert_equal ClassCacheTest, @cache.get(ClassCacheTest)
end
def test_get_with_name
- @cache[ClassCacheTest] = ClassCacheTest
- assert_equal ClassCacheTest, @cache[ClassCacheTest.name]
+ @cache.store(ClassCacheTest)
+ assert_equal ClassCacheTest, @cache.get(ClassCacheTest.name)
end
def test_get_constantizes
assert @cache.empty?
- assert_equal ClassCacheTest, @cache[ClassCacheTest.name]
+ assert_equal ClassCacheTest, @cache.get(ClassCacheTest.name)
end
- def test_get_is_an_alias
- assert_equal @cache[ClassCacheTest], @cache.get(ClassCacheTest.name)
+ def test_get_constantizes_fails_on_invalid_names
+ assert @cache.empty?
+ assert_raise NameError do
+ @cache.get("OmgTotallyInvalidConstantName")
+ end
+ end
+
+ def test_get_alias
+ assert @cache.empty?
+ assert_equal @cache[ClassCacheTest.name], @cache.get(ClassCacheTest.name)
+ end
+
+ def test_safe_get_constantizes
+ assert @cache.empty?
+ assert_equal ClassCacheTest, @cache.safe_get(ClassCacheTest.name)
+ end
+
+ def test_safe_get_constantizes_doesnt_fail_on_invalid_names
+ assert @cache.empty?
+ assert_equal nil, @cache.safe_get("OmgTotallyInvalidConstantName")
end
def test_new_rejects_strings
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index b4f848cd44..c040d86327 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -57,6 +57,16 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
assert_equal Date.new(2005,11,28), Date.new(2005,12,04).beginning_of_week #sunday
end
+ def test_monday
+ assert_equal Date.new(2005,11,28), Date.new(2005,11,28).monday
+ assert_equal Date.new(2005,11,28), Date.new(2005,12,01).monday
+ end
+
+ def test_sunday
+ assert_equal Date.new(2008,3,2), Date.new(2008,3,02).sunday
+ assert_equal Date.new(2008,3,2), Date.new(2008,2,29).sunday
+ end
+
def test_beginning_of_week_in_calendar_reform
assert_equal Date.new(1582,10,1), Date.new(1582,10,15).beginning_of_week #friday
end
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index 9be28272f7..0087163faf 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -54,6 +54,24 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase
assert_equal 86399,DateTime.civil(2005,1,1,23,59,59).seconds_since_midnight
end
+ def test_days_to_week_start
+ assert_equal 0, Time.local(2011,11,01,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 1, Time.local(2011,11,02,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 2, Time.local(2011,11,03,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 3, Time.local(2011,11,04,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 4, Time.local(2011,11,05,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 5, Time.local(2011,11,06,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 6, Time.local(2011,11,07,0,0,0).days_to_week_start(:tuesday)
+
+ assert_equal 3, Time.local(2011,11,03,0,0,0).days_to_week_start(:monday)
+ assert_equal 3, Time.local(2011,11,04,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 3, Time.local(2011,11,05,0,0,0).days_to_week_start(:wednesday)
+ assert_equal 3, Time.local(2011,11,06,0,0,0).days_to_week_start(:thursday)
+ assert_equal 3, Time.local(2011,11,07,0,0,0).days_to_week_start(:friday)
+ assert_equal 3, Time.local(2011,11,8,0,0,0).days_to_week_start(:saturday)
+ assert_equal 3, Time.local(2011,11,9,0,0,0).days_to_week_start(:sunday)
+ end
+
def test_beginning_of_week
assert_equal DateTime.civil(2005,1,31), DateTime.civil(2005,2,4,10,10,10).beginning_of_week
assert_equal DateTime.civil(2005,11,28), DateTime.civil(2005,11,28,0,0,0).beginning_of_week #monday
diff --git a/activesupport/test/core_ext/object/inclusion_test.rb b/activesupport/test/core_ext/object/inclusion_test.rb
index 1de857d678..568ebe9aab 100644
--- a/activesupport/test/core_ext/object/inclusion_test.rb
+++ b/activesupport/test/core_ext/object/inclusion_test.rb
@@ -2,6 +2,16 @@ require 'abstract_unit'
require 'active_support/core_ext/object/inclusion'
class InTest < Test::Unit::TestCase
+ def test_in_multiple_args
+ assert :b.in?(:a,:b)
+ assert !:c.in?(:a,:b)
+ end
+
+ def test_in_multiple_arrays
+ assert [1,2].in?([1,2],[2,3])
+ assert ![1,2].in?([1,3],[2,1])
+ end
+
def test_in_array
assert 1.in?([1,2])
assert !3.in?([1,2])
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index d2ff44e1b4..6cc63851e9 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -59,8 +59,28 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.local(2005,11,28), Time.local(2005,12,02,0,0,0).beginning_of_week #friday
assert_equal Time.local(2005,11,28), Time.local(2005,12,03,0,0,0).beginning_of_week #saturday
assert_equal Time.local(2005,11,28), Time.local(2005,12,04,0,0,0).beginning_of_week #sunday
+
end
+ def test_days_to_week_start
+ assert_equal 0, Time.local(2011,11,01,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 1, Time.local(2011,11,02,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 2, Time.local(2011,11,03,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 3, Time.local(2011,11,04,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 4, Time.local(2011,11,05,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 5, Time.local(2011,11,06,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 6, Time.local(2011,11,07,0,0,0).days_to_week_start(:tuesday)
+
+ assert_equal 3, Time.local(2011,11,03,0,0,0).days_to_week_start(:monday)
+ assert_equal 3, Time.local(2011,11,04,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 3, Time.local(2011,11,05,0,0,0).days_to_week_start(:wednesday)
+ assert_equal 3, Time.local(2011,11,06,0,0,0).days_to_week_start(:thursday)
+ assert_equal 3, Time.local(2011,11,07,0,0,0).days_to_week_start(:friday)
+ assert_equal 3, Time.local(2011,11,8,0,0,0).days_to_week_start(:saturday)
+ assert_equal 3, Time.local(2011,11,9,0,0,0).days_to_week_start(:sunday)
+ end
+
+
def test_beginning_of_day
assert_equal Time.local(2005,2,4,0,0,0), Time.local(2005,2,4,10,10,10).beginning_of_day
with_env_tz 'US/Eastern' do
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index a7afb72562..6b0be4c096 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,27 +1,27 @@
## Rails 3.2.0 (unreleased) ##
-* Added displaying of mounted engine's routes with `rake routes ENGINES=true`. *Piotr Sarnacki*
+* Display mounted engine's routes in `rake routes`. *Piotr Sarnacki*
-* Allow to change the loading order of railties with `config.railties_order=`. *Piotr Sarnacki*
+* Allow to change the loading order of railties with `config.railties_order=` *Piotr Sarnacki*
Example:
config.railties_order = [Blog::Engine, :main_app, :all]
* Scaffold returns 204 No Content for API requests without content. This makes scaffold work with jQuery out of the box. *José Valim*
-* Updated Rails::Rack::Logger middleware to apply any tags set in config.log_tags to the newly ActiveSupport::TaggedLogging Rails.logger. This makes it easy to tag log lines with debug information like subdomain and request id -- both very helpful in debugging multi-user production applications *DHH*
+* Update Rails::Rack::Logger middleware to apply any tags set in config.log_tags to the newly ActiveSupport::TaggedLogging Rails.logger. This makes it easy to tag log lines with debug information like subdomain and request id -- both very helpful in debugging multi-user production applications *DHH*
* Default options to `rails new` can be set in ~/.railsrc *Guillermo Iguaran*
-* Added destroy alias to Rails engines. *Guillermo Iguaran*
+* Add destroy alias to Rails engines *Guillermo Iguaran*
-* Added destroy alias for Rails command line. This allows the following: `rails d model post`. *Andrey Ognevsky*
+* Add destroy alias for Rails command line. This allows the following: `rails d model post` *Andrey Ognevsky*
* Attributes on scaffold and model generators default to string. This allows the following: "rails g scaffold Post title body:text author" *José Valim*
-* Removed old plugin generator (`rails generate plugin`) in favor of `rails plugin new` command. *Guillermo Iguaran*
+* Remove old plugin generator (`rails generate plugin`) in favor of `rails plugin new` command *Guillermo Iguaran*
-* Removed old 'config.paths.app.controller' API in favor of 'config.paths["app/controller"]' API. *Guillermo Iguaran*
+* Remove old 'config.paths.app.controller' API in favor of 'config.paths["app/controller"]' API *Guillermo Iguaran*
* Rails 3.1.1
diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile
index ad12dca7e8..c4724f182e 100644
--- a/railties/guides/source/active_record_querying.textile
+++ b/railties/guides/source/active_record_querying.textile
@@ -1287,6 +1287,7 @@ User.where(:id => 1).joins(:posts).explain
may yield
<plain>
+EXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `posts` ON `posts`.`user_id` = `users`.`id` WHERE `users`.`id` = 1
<plus>----<plus>-------------<plus>-------<plus>-------<plus>---------------<plus>---------<plus>---------<plus>-------<plus>------<plus>-------------<plus>
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
<plus>----<plus>-------------<plus>-------<plus>-------<plus>---------------<plus>---------<plus>---------<plus>-------<plus>------<plus>-------------<plus>
@@ -1302,6 +1303,7 @@ Active Record performs a pretty printing that emulates the one of the database
shells. So, the same query running with the PostgreSQL adapter would yield instead
<plain>
+EXPLAIN for: SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" WHERE "users"."id" = 1
QUERY PLAN
------------------------------------------------------------------------------
Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
@@ -1324,12 +1326,15 @@ User.where(:id => 1).includes(:posts).explain
yields
<plain>
+EXPLAIN for: SELECT `users`.* FROM `users` WHERE `users`.`id` = 1
<plus>----<plus>-------------<plus>-------<plus>-------<plus>---------------<plus>---------<plus>---------<plus>-------<plus>------<plus>-------<plus>
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
<plus>----<plus>-------------<plus>-------<plus>-------<plus>---------------<plus>---------<plus>---------<plus>-------<plus>------<plus>-------<plus>
| 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
<plus>----<plus>-------------<plus>-------<plus>-------<plus>---------------<plus>---------<plus>---------<plus>-------<plus>------<plus>-------<plus>
1 row in set (0.00 sec)
+
+EXPLAIN for: SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` IN (1)
<plus>----<plus>-------------<plus>-------<plus>------<plus>---------------<plus>------<plus>---------<plus>------<plus>------<plus>-------------<plus>
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
<plus>----<plus>-------------<plus>-------<plus>------<plus>---------------<plus>------<plus>---------<plus>------<plus>------<plus>-------------<plus>
@@ -1339,3 +1344,14 @@ yields
</plain>
under MySQL.
+
+h4. Interpreting EXPLAIN
+
+Interpretation of the output of EXPLAIN is beyond the scope of this guide. The
+following pointers may be helpful:
+
+* SQLite3: "EXPLAIN QUERY PLAN":http://www.sqlite.org/eqp.html
+
+* MySQL: "EXPLAIN Output Format":http://dev.mysql.com/doc/refman/5.6/en/explain-output.html
+
+* PostgreSQL: "Using EXPLAIN":http://www.postgresql.org/docs/current/static/using-explain.html
diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile
index ff6c5f967f..2dee440b3b 100644
--- a/railties/guides/source/active_support_core_extensions.textile
+++ b/railties/guides/source/active_support_core_extensions.textile
@@ -571,7 +571,7 @@ NOTE: Defined in +active_support/core_ext/module/attr_accessor_with_default.rb+.
h5. Internal Attributes
-When you are defining an attribute in a class that is meant to be subclassed name collisions are a risk. That's remarkably important for libraries.
+When you are defining an attribute in a class that is meant to be subclassed, name collisions are a risk. That's remarkably important for libraries.
Active Support defines the macros +attr_internal_reader+, +attr_internal_writer+, and +attr_internal_accessor+. They behave like their Ruby built-in +attr_*+ counterparts, except they name the underlying instance variable in a way that makes collisions less likely.
@@ -3039,15 +3039,30 @@ Active Support defines these methods as well for Ruby 1.8.
h6. +beginning_of_week+, +end_of_week+
-The methods +beginning_of_week+ and +end_of_week+ return the dates for the beginning and end of week, assuming weeks start on Monday:
+The methods +beginning_of_week+ and +end_of_week+ return the dates for the
+beginning and end of the week, respectively. Weeks are assumed to start on
+Monday, but that can be changed passing an argument.
<ruby>
-d = Date.new(2010, 5, 8) # => Sat, 08 May 2010
-d.beginning_of_week # => Mon, 03 May 2010
-d.end_of_week # => Sun, 09 May 2010
+d = Date.new(2010, 5, 8) # => Sat, 08 May 2010
+d.beginning_of_week # => Mon, 03 May 2010
+d.beginning_of_week(:sunday) # => Sun, 02 May 2010
+d.end_of_week # => Sun, 09 May 2010
+d.end_of_week(:sunday) # => Sat, 08 May 2010
</ruby>
-+beginning_of_week+ is aliased to +monday+ and +at_beginning_of_week+. +end_of_week+ is aliased to +sunday+ and +at_end_of_week+.
++beginning_of_week+ is aliased to +at_beginning_of_week+ and +end_of_week+ is aliased to +at_end_of_week+.
+
+h6. +monday+, +sunday+
+
+The methods +monday+ and +sunday+ return the dates for the beginning and
+end of the week, respectively. Weeks are assumed to start on Monday.
+
+<ruby>
+d = Date.new(2010, 5, 8) # => Sat, 08 May 2010
+d.monday # => Mon, 03 May 2010
+d.sunday # => Sun, 09 May 2010
+</ruby>
h6. +prev_week+, +next_week+
@@ -3272,8 +3287,10 @@ The class +DateTime+ is a subclass of +Date+ so by loading +active_support/core_
<ruby>
yesterday
tomorrow
-beginning_of_week (monday, at_beginning_of_week)
-end_on_week (at_end_of_week)
+beginning_of_week (at_beginning_of_week)
+end_of_week (at_end_of_week)
+monday
+sunday
weeks_ago
prev_week
next_week
@@ -3446,8 +3463,10 @@ ago
since (in)
beginning_of_day (midnight, at_midnight, at_beginning_of_day)
end_of_day
-beginning_of_week (monday, at_beginning_of_week)
-end_on_week (at_end_of_week)
+beginning_of_week (at_beginning_of_week)
+end_of_week (at_end_of_week)
+monday
+sunday
weeks_ago
prev_week
next_week
diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile
index cd6e7d116e..809948b41e 100644
--- a/railties/guides/source/configuring.textile
+++ b/railties/guides/source/configuring.textile
@@ -263,6 +263,8 @@ h4. Configuring Active Record
* +config.active_record.whitelist_attributes+ will create an empty whitelist of attributes available for mass-assignment security for all models in your app.
+* +config.active_record.identity_map+ controls whether the identity map is enabled, and is false by default.
+
The MySQL adapter adds one additional configuration option:
* +ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans+ controls whether Active Record will consider all +tinyint(1)+ columns in a MySQL database to be booleans and is true by default.
diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile
index fde83ae730..ca6a404212 100644
--- a/railties/guides/source/getting_started.textile
+++ b/railties/guides/source/getting_started.textile
@@ -450,6 +450,8 @@ start a web server on your development machine. You can do this by running:
$ rails server
</shell>
+TIP: Compiling CoffeeScript to JavaScript requires a JavaScript runtime and the absence of a runtime will give you an +execjs+ error. Usually Mac OS X and Windows come with a JavaScript runtime installed. +therubyracer+ and +therubyrhino+ are the commonly used runtimes for Ruby and JRuby respectively. You can also investigate a list of runtimes at "ExecJS":https://github.com/sstephenson/execjs.
+
This will fire up an instance of the WEBrick web server by default (Rails can
also use several other web servers). To see your application in action, open a
browser window and navigate to "http://localhost:3000":http://localhost:3000.
diff --git a/railties/guides/source/initialization.textile b/railties/guides/source/initialization.textile
index 036b356a37..5ae9cf0f2b 100644
--- a/railties/guides/source/initialization.textile
+++ b/railties/guides/source/initialization.textile
@@ -17,7 +17,7 @@ As of Rails 3, +script/server+ has become +rails server+. This was done to centr
h4. +bin/rails+
-The actual +rails+ command is kept in _bin/rails_ at the and goes like this:
+The actual +rails+ command is kept in _bin/rails_:
<ruby>
#!/usr/bin/env ruby
@@ -31,7 +31,7 @@ rescue LoadError
end
</ruby>
-This file will attempt to load +rails/cli+ and if it cannot find it then add the +railties/lib+ path to the load path (+$:+) and will then try to require it again.
+This file will attempt to load +rails/cli+. If it cannot find it then +railties/lib+ is added to the load path (+$:+) before retrying.
h4. +railties/lib/rails/cli.rb+
@@ -56,7 +56,7 @@ else
end
</ruby>
-The +rbconfig+ file here is out of Ruby's standard library and provides us with the +RbConfig+ class which contains useful information dependent on how Ruby was compiled. We'll see this in use in +railties/lib/rails/script_rails_loader+.
+The +rbconfig+ file from the Ruby standard library provides us with the +RbConfig+ class which contains detailed information about the Ruby environment, including how Ruby was compiled. We can see this in use in +railties/lib/rails/script_rails_loader+.
<ruby>
require 'pathname'
@@ -71,7 +71,7 @@ module Rails
end
</ruby>
-The +rails/script_rails_loader+ file uses +RbConfig::Config+ to gather up the +bin_dir+ and +ruby_install_name+ values for the configuration which will result in a path such as +/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby+, which is the default path on Mac OS X. If you're running Windows the path may be something such as +C:/Ruby192/bin/ruby+. Anyway, the path on your system may be different, but the point of this is that it will point at the known ruby executable location for your install. The +RbConfig::CONFIG["EXEEXT"]+ will suffix this path with ".exe" if the script is running on Windows. This constant is used later on in +exec_script_rails!+. As for the +SCRIPT_RAILS+ constant, we'll see that when we get to the +in_rails_application?+ method.
+The +rails/script_rails_loader+ file uses +RbConfig::Config+ to obtain the +bin_dir+ and +ruby_install_name+ values for the configuration which together form the path to the Ruby interpreter. The +RbConfig::CONFIG["EXEEXT"]+ will suffix this path with ".exe" if the script is running on Windows. This constant is used later on in +exec_script_rails!+. As for the +SCRIPT_RAILS+ constant, we'll see that when we get to the +in_rails_application?+ method.
Back in +rails/cli+, the next line is this:
@@ -79,7 +79,7 @@ Back in +rails/cli+, the next line is this:
Rails::ScriptRailsLoader.exec_script_rails!
</ruby>
-This method is defined in +rails/script_rails_loader+ like this:
+This method is defined in +rails/script_rails_loader+:
<ruby>
def self.exec_script_rails!
@@ -96,7 +96,7 @@ rescue SystemCallError
end
</ruby>
-This method will first check if the current working directory (+cwd+) is a Rails application or is a subdirectory of one. The way to determine this is defined in the +in_rails_application?+ method like this:
+This method will first check if the current working directory (+cwd+) is a Rails application or a subdirectory of one. This is determined by the +in_rails_application?+ method:
<ruby>
def self.in_rails_application?
@@ -104,7 +104,7 @@ def self.in_rails_application?
end
</ruby>
-The +SCRIPT_RAILS+ constant defined earlier is used here, with +File.exists?+ checking for its presence in the current directory. If this method returns +false+, then +in_rails_application_subdirectory?+ will be used:
+The +SCRIPT_RAILS+ constant defined earlier is used here, with +File.exists?+ checking for its presence in the current directory. If this method returns +false+ then +in_rails_application_subdirectory?+ will be used:
<ruby>
def self.in_rails_application_subdirectory?(path = Pathname.new(Dir.pwd))
@@ -112,17 +112,17 @@ def self.in_rails_application_subdirectory?(path = Pathname.new(Dir.pwd))
end
</ruby>
-This climbs the directory tree until it reaches a path which contains a +script/rails+ file. If a directory is reached which contains this file then this line will run:
+This climbs the directory tree until it reaches a path which contains a +script/rails+ file. If a directory containing this file is reached then this line will run:
<ruby>
exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application?
</ruby>
-This is effectively the same as doing +ruby script/rails [arguments]+. Where +[arguments]+ at this point in time is simply "server".
+This is effectively the same as running +ruby script/rails [arguments]+, where +[arguments]+ at this point in time is simply "server".
h4. +script/rails+
-This file looks like this:
+This file is as follows:
<ruby>
APP_PATH = File.expand_path('../../config/application', __FILE__)
@@ -130,7 +130,7 @@ require File.expand_path('../../config/boot', __FILE__)
require 'rails/commands'
</ruby>
-The +APP_PATH+ constant here will be used later in +rails/commands+. The +config/boot+ file that +script/rails+ references is the +config/boot.rb+ file in our application which is responsible for loading Bundler and setting it up.
+The +APP_PATH+ constant will be used later in +rails/commands+. The +config/boot+ file referenced here is the +config/boot.rb+ file in our application which is responsible for loading Bundler and setting it up.
h4. +config/boot.rb+
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 847445d29f..25ff74506a 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -123,7 +123,8 @@ module Rails
@env_config ||= super.merge({
"action_dispatch.parameter_filter" => config.filter_parameters,
"action_dispatch.secret_token" => config.secret_token,
- "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions
+ "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions,
+ "action_dispatch.logger" => Rails.logger
})
end
diff --git a/railties/lib/rails/application/route_inspector.rb b/railties/lib/rails/application/route_inspector.rb
index 8da7de2584..26652a8e5e 100644
--- a/railties/lib/rails/application/route_inspector.rb
+++ b/railties/lib/rails/application/route_inspector.rb
@@ -46,7 +46,7 @@ module Rails
end
def collect_engine_routes(name, rack_app)
- return unless rack_app && ENV["ENGINES"] && rack_app.respond_to?(:routes)
+ return unless rack_app && rack_app.respond_to?(:routes)
return if @engines[name]
routes = rack_app.routes
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index b26839644e..ca93f9ef9d 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -68,8 +68,9 @@ module Rails
end
in_root do
- str = "gem #{parts.join(", ")}\n"
+ str = "gem #{parts.join(", ")}"
str = " " + str if @in_group
+ str = "\n" + str
append_file "Gemfile", str, :verbose => false
end
end
@@ -87,13 +88,13 @@ module Rails
log :gemfile, "group #{name}"
in_root do
- append_file "Gemfile", "\ngroup #{name} do\n", :force => true
+ append_file "Gemfile", "\ngroup #{name} do", :force => true
@in_group = true
instance_eval(&block)
@in_group = false
- append_file "Gemfile", "end\n", :force => true
+ append_file "Gemfile", "\nend\n", :force => true
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/public/index.html b/railties/lib/rails/generators/rails/app/templates/public/index.html
index 9d9811a5bf..a1d50995c5 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/index.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/index.html
@@ -59,7 +59,7 @@
#header {
- background-image: url("/assets/rails.png");
+ background-image: url("assets/rails.png");
background-repeat: no-repeat;
background-position: top left;
height: 64px;
diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb
index b34bc4a524..3c5b39fa16 100644
--- a/railties/lib/rails/generators/resource_helpers.rb
+++ b/railties/lib/rails/generators/resource_helpers.rb
@@ -64,7 +64,7 @@ module Rails
end
begin
- "#{options[:orm].to_s.classify}::Generators::ActiveModel".constantize
+ "#{options[:orm].to_s.camelize}::Generators::ActiveModel".constantize
rescue NameError
Rails::Generators::ActiveModel
end
@@ -73,7 +73,7 @@ module Rails
# Initialize ORM::Generators::ActiveModel to access instance methods.
def orm_instance(name=singular_table_name)
- @orm_instance ||= @orm_class.new(name)
+ @orm_instance ||= orm_class.new(name)
end
end
end
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index f0991b99af..07a122e7d0 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -181,7 +181,7 @@ module Rails
def load_tasks(app=self)
extend Rake::DSL if defined? Rake::DSL
- self.class.rake_tasks.each { |block| block.call(app) }
+ self.class.rake_tasks.each { |block| self.instance_exec(app, &block) }
# load also tasks from all superclasses
klass = self.class.superclass
diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake
index df72baef67..7dc54144da 100644
--- a/railties/lib/rails/tasks/routes.rake
+++ b/railties/lib/rails/tasks/routes.rake
@@ -1,4 +1,4 @@
-desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x. Include engine\'s routes with ENGINES=true'
+desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.'
task :routes => :environment do
Rails.application.reload_routes!
all_routes = Rails.application.routes.routes
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index f356805d6e..f37a024a0b 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -524,6 +524,7 @@ module ApplicationTests
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
+ assert_equal app.env_config['action_dispatch.logger'], Rails.logger
end
end
end
diff --git a/railties/test/application/route_inspect_test.rb b/railties/test/application/route_inspect_test.rb
index 130d4e52f8..2ad5ee6c4c 100644
--- a/railties/test/application/route_inspect_test.rb
+++ b/railties/test/application/route_inspect_test.rb
@@ -11,8 +11,6 @@ module ApplicationTests
end
def test_displaying_routes_for_engines
- ENV["ENGINES"] = "true"
-
engine = Class.new(Rails::Engine) do
def self.to_s
"Blog::Engine"
@@ -35,8 +33,6 @@ module ApplicationTests
"cart GET /cart(.:format) cart#show"
]
assert_equal expected, output
- ensure
- ENV["ENGINES"] = nil
end
def test_cart_inspect
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index 51fa2fe16f..c1fd6a38f1 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -95,11 +95,13 @@ class ActionsTest < Rails::Generators::TestCase
def test_gem_should_insert_on_separate_lines
run_generator
+ File.open('Gemfile', 'a') {|f| f.write('# Some content...') }
+
action :gem, 'rspec'
action :gem, 'rspec-rails'
- assert_file 'Gemfile', /gem "rspec"$/
- assert_file 'Gemfile', /gem "rspec-rails"$/
+ assert_file 'Gemfile', /^gem "rspec"$/
+ assert_file 'Gemfile', /^gem "rspec-rails"$/
end
def test_gem_group_should_wrap_gems_in_a_group
@@ -112,7 +114,7 @@ class ActionsTest < Rails::Generators::TestCase
action :gem_group, :test do
gem 'fakeweb'
end
-
+
assert_file 'Gemfile', /\ngroup :development, :test do\n gem "rspec-rails"\nend\n\ngroup :test do\n gem "fakeweb"\nend/
end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index a1bd2fbaa5..235c08e38e 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -55,6 +55,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "app/views/layouts/application.html.erb", /javascript_include_tag\s+"application"/
assert_file "app/assets/stylesheets/application.css"
assert_file "config/application.rb", /config\.assets\.enabled = true/
+ assert_file "public/index.html", /url\("assets\/rails.png"\);/
end
def test_invalid_application_name_raises_an_error
diff --git a/railties/test/generators/orm_test.rb b/railties/test/generators/orm_test.rb
new file mode 100644
index 0000000000..9dd3d3e0ec
--- /dev/null
+++ b/railties/test/generators/orm_test.rb
@@ -0,0 +1,38 @@
+require "generators/generators_test_helper"
+require "rails/generators/rails/scaffold_controller/scaffold_controller_generator"
+
+# Mock out two ORMs
+module ORMWithGenerators
+ module Generators
+ class ActiveModel
+ def initialize(name)
+ end
+ end
+ end
+end
+
+module ORMWithoutGenerators
+ # No generators
+end
+
+class OrmTest < Rails::Generators::TestCase
+ include GeneratorsTestHelper
+ tests Rails::Generators::ScaffoldControllerGenerator
+
+ def test_orm_class_returns_custom_generator_if_supported_custom_orm_set
+ g = generator ["Foo"], :orm => "ORMWithGenerators"
+ assert_equal ORMWithGenerators::Generators::ActiveModel, g.send(:orm_class)
+ end
+
+ def test_orm_class_returns_rails_generator_if_unsupported_custom_orm_set
+ g = generator ["Foo"], :orm => "ORMWithoutGenerators"
+ assert_equal Rails::Generators::ActiveModel, g.send(:orm_class)
+ end
+
+ def test_orm_instance_returns_orm_class_instance_with_name
+ g = generator ["Foo"]
+ orm_instance = g.send(:orm_instance)
+ assert g.send(:orm_class) === orm_instance
+ assert_equal "foo", orm_instance.name
+ end
+end