aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile7
-rwxr-xr-xRakefile22
-rw-r--r--actionpack/CHANGELOG49
-rw-r--r--actionpack/lib/action_dispatch/http/headers.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb12
-rw-r--r--actionpack/lib/action_dispatch/routing.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/controller_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb13
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb68
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb11
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/translation_helper.rb2
-rw-r--r--actionpack/lib/sprockets/railtie.rb15
-rw-r--r--actionpack/test/abstract/abstract_controller_test.rb2
-rw-r--r--actionpack/test/controller/test_test.rb1
-rw-r--r--actionpack/test/controller/url_for_test.rb4
-rw-r--r--actionpack/test/controller/webservice_test.rb4
-rw-r--r--actionpack/test/dispatch/uploaded_file_test.rb7
-rw-r--r--actionpack/test/template/form_helper_test.rb4
-rw-r--r--actionpack/test/template/form_options_helper_test.rb20
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb4
-rw-r--r--actionpack/test/template/translation_helper_test.rb8
-rw-r--r--actionpack/test/template/url_helper_test.rb8
-rw-r--r--activemodel/CHANGELOG3
-rw-r--r--activemodel/lib/active_model/naming.rb7
-rw-r--r--activemodel/lib/active_model/translation.rb5
-rw-r--r--activemodel/test/cases/attribute_methods_test.rb1
-rw-r--r--activemodel/test/cases/translation_test.rb10
-rw-r--r--activerecord/RUNNING_UNIT_TESTS5
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb67
-rw-r--r--activerecord/lib/active_record/base.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb2
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb32
-rw-r--r--activerecord/lib/active_record/test_case.rb17
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb63
-rw-r--r--activerecord/test/cases/autosave_association_test.rb2
-rw-r--r--activerecord/test/cases/helper.rb13
-rw-r--r--activerecord/test/cases/mass_assignment_security_test.rb305
-rw-r--r--activerecord/test/models/company.rb12
-rw-r--r--activerecord/test/models/person.rb8
-rw-r--r--activerecord/test/support/config.rb12
-rw-r--r--activesupport/CHANGELOG8
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute_accessors.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb49
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb27
-rw-r--r--activesupport/lib/active_support/dependencies.rb11
-rw-r--r--activesupport/lib/active_support/memoizable.rb13
-rw-r--r--activesupport/test/core_ext/class/attribute_accessor_test.rb11
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb8
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb20
-rw-r--r--activesupport/test/flush_cache_on_private_memoization_test.rb4
-rw-r--r--activesupport/test/memoizable_test.rb38
-rw-r--r--activesupport/test/safe_buffer_test.rb42
-rw-r--r--railties/CHANGELOG2
-rw-r--r--railties/guides/source/active_support_core_extensions.textile22
-rw-r--r--railties/guides/source/configuring.textile2
-rw-r--r--railties/lib/rails/application.rb6
-rw-r--r--railties/lib/rails/application/railties.rb2
-rw-r--r--railties/lib/rails/commands/plugin.rb4
-rw-r--r--railties/lib/rails/engine.rb12
-rw-r--r--railties/lib/rails/generators/app_base.rb2
-rw-r--r--railties/lib/rails/generators/generated_attribute.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/routes.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb14
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/application.html.erb.tt14
-rw-r--r--railties/lib/rails/railtie.rb6
-rw-r--r--railties/test/application/initializers/i18n_test.rb6
-rw-r--r--railties/test/generators/generated_attribute_test.rb13
-rw-r--r--railties/test/generators/model_generator_test.rb12
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb30
-rw-r--r--railties/test/generators/scaffold_generator_test.rb12
74 files changed, 956 insertions, 309 deletions
diff --git a/Gemfile b/Gemfile
index f7ea906827..fc4fdb17f5 100644
--- a/Gemfile
+++ b/Gemfile
@@ -26,6 +26,9 @@ gem "memcache-client", ">= 1.8.5"
platforms :mri_18 do
gem "system_timer"
+ # ruby-debug requires linecache which depends on require_relative but doesn't explicitly
+ # declare this in its gemspec
+ gem "require_relative"
gem "ruby-debug", ">= 0.10.3"
gem "json"
end
@@ -41,7 +44,7 @@ platforms :ruby do
end
gem "json"
gem "yajl-ruby"
- gem "nokogiri", ">= 1.4.4"
+ gem "nokogiri", ">= 1.4.5"
group :test do
gem "ruby-prof" if RUBY_VERSION < "1.9.3"
@@ -53,7 +56,7 @@ platforms :ruby do
group :db do
gem "pg", ">= 0.11.0"
gem "mysql", ">= 2.8.1"
- gem "mysql2", ">= 0.3.0"
+ gem "mysql2", ">= 0.3.6"
end
end
diff --git a/Rakefile b/Rakefile
index 92b2e77963..ba55e580f4 100755
--- a/Rakefile
+++ b/Rakefile
@@ -51,9 +51,27 @@ desc "Generate documentation for the Rails framework"
RDoc::Task.new do |rdoc|
RDOC_MAIN = 'RDOC_MAIN.rdoc'
+ # This is a hack.
+ #
+ # Backslashes are needed to prevent RDoc from autolinking "Rails" to the
+ # documentation of the Rails module. On the other hand, as of this
+ # writing README.rdoc is displayed in the front page of the project in
+ # GitHub, where backslashes are shown and look weird.
+ #
+ # The temporary solution is to have a README.rdoc without backslashes for
+ # GitHub, and gsub it to generate the main page of the API.
+ #
+ # The idea for the future is to have totally different files, since the
+ # API is no longer a generic entry point to Rails and deserves a
+ # dedicated main page specifically thought as an API entry point.
rdoc.before_running_rdoc do
rdoc_main = File.read('README.rdoc')
- rdoc_main.gsub!(/\b(?=Rails)\b/) { '\\' }
+
+ # The ^(?=\S) assertion prevents code blocks from being processed,
+ # since no autolinking happens there and RDoc displays the backslash
+ # otherwise.
+ rdoc_main.gsub!(/^(?=\S).*?\b(?=Rails)\b/) { "#$&\\" }
+
File.open(RDOC_MAIN, 'w') do |f|
f.write(rdoc_main)
end
@@ -72,7 +90,7 @@ RDoc::Task.new do |rdoc|
rdoc.rdoc_files.include('railties/MIT-LICENSE')
rdoc.rdoc_files.include('railties/README.rdoc')
rdoc.rdoc_files.include('railties/lib/**/*.rb')
- rdoc.rdoc_files.exclude('railties/lib/rails/generators/**/templates/*')
+ rdoc.rdoc_files.exclude('railties/lib/rails/generators/**/templates/**/*.rb')
rdoc.rdoc_files.include('activerecord/README.rdoc')
rdoc.rdoc_files.include('activerecord/CHANGELOG')
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 5314dcc193..f4b6464bdc 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,33 @@
*Rails 3.2.0 (unreleased)*
+* Generate hidden input before select with :multiple option set to true.
+ This is useful when you rely on the fact that when no options is set,
+ the state of select will be sent to rails application. Without hidden field
+ nothing is sent according to HTML spec [Bogdan Gusiev]
+
+* Refactor ActionController::TestCase cookies [Andrew White]
+
+ Assigning cookies for test cases should now use cookies[], e.g:
+
+ cookies[:email] = 'user@example.com'
+ get :index
+ assert_equal 'user@example.com', cookies[:email]
+
+ To clear the cookies, use clear, e.g:
+
+ cookies.clear
+ get :index
+ assert_nil cookies[:email]
+
+ We now no longer write out HTTP_COOKIE and the cookie jar is
+ persistent between requests so if you need to manipulate the environment
+ for your test you need to do it before the cookie jar is created.
+
+
+*Rails 3.1.0 (unreleased)*
+
+* json_escape will now return a SafeBuffer string if it receives SafeBuffer string [tenderlove]
+
* Make sure escape_js returns SafeBuffer string if it receives SafeBuffer string [Prem Sichanugrist]
* Fix escape_js to work correctly with the new SafeBuffer restriction [Paul Gallagher]
@@ -31,27 +59,6 @@
You can read more about this change in http://groups.google.com/group/rubyonrails-security/browse_thread/thread/2e516e7acc96c4fb
-* Refactor ActionController::TestCase cookies [Andrew White]
-
- Assigning cookies for test cases should now use cookies[], e.g:
-
- cookies[:email] = 'user@example.com'
- get :index
- assert_equal 'user@example.com', cookies[:email]
-
- To clear the cookies, use clear, e.g:
-
- cookies.clear
- get :index
- assert_nil cookies[:email]
-
- We now no longer write out HTTP_COOKIE and the cookie jar is
- persistent between requests so if you need to manipulate the environment
- for your test you need to do it before the cookie jar is created.
-
-
-*Rails 3.1.0 (unreleased)*
-
* Added 'ActionView::Helpers::FormHelper.fields_for_with_index', similar to fields_for but allows to have access to the current iteration index [Jorge Bejar]
* Warn if we cannot verify CSRF token authenticity [José Valim]
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb
index 1e43104f0a..505d5560b1 100644
--- a/actionpack/lib/action_dispatch/http/headers.rb
+++ b/actionpack/lib/action_dispatch/http/headers.rb
@@ -3,9 +3,10 @@ require 'active_support/memoizable'
module ActionDispatch
module Http
class Headers < ::Hash
- extend ActiveSupport::Memoizable
+ @@env_cache = Hash.new { |h,k| h[k] = "HTTP_#{k.upcase.gsub(/-/, '_')}" }
def initialize(*args)
+
if args.size == 1 && args[0].is_a?(Hash)
super()
update(args[0])
@@ -25,9 +26,8 @@ module ActionDispatch
private
# Converts a HTTP header name to an environment variable name.
def env_name(header_name)
- "HTTP_#{header_name.upcase.gsub(/-/, '_')}"
+ @@env_cache[header_name]
end
- memoize :env_name
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 37effade4f..a15ad28f16 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -4,7 +4,7 @@ module ActionDispatch
attr_accessor :original_filename, :content_type, :tempfile, :headers
def initialize(hash)
- @original_filename = hash[:filename]
+ @original_filename = encode_filename(hash[:filename])
@content_type = hash[:type]
@headers = hash[:head]
@tempfile = hash[:tempfile]
@@ -30,6 +30,16 @@ module ActionDispatch
def size
@tempfile.size
end
+
+ private
+ def encode_filename(filename)
+ # Encode the filename in the utf8 encoding, unless it is nil or we're in 1.8
+ if "ruby".encoding_aware? && filename
+ filename.force_encoding("UTF-8").encode!
+ else
+ filename
+ end
+ end
end
module Upload
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 74c090f260..1dcd83ceb5 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -161,7 +161,7 @@ module ActionDispatch
# Consider the following route, which you will find commented out at the
# bottom of your generated <tt>config/routes.rb</tt>:
#
- # match ':controller(/:action(/:id(.:format)))'
+ # match ':controller(/:action(/:id))(.:format)'
#
# This route states that it expects requests to consist of a
# <tt>:controller</tt> followed optionally by an <tt>:action</tt> that in
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 70ea419e81..65895590bf 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -87,7 +87,7 @@ module ActionDispatch
raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
end
end
- end
+ end
# match "account/overview"
def using_match_shorthand?(path, options)
diff --git a/actionpack/lib/action_view/helpers/controller_helper.rb b/actionpack/lib/action_view/helpers/controller_helper.rb
index db59bca159..1a583e62ae 100644
--- a/actionpack/lib/action_view/helpers/controller_helper.rb
+++ b/actionpack/lib/action_view/helpers/controller_helper.rb
@@ -20,4 +20,4 @@ module ActionView
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 56e3af683b..6a724749f4 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -621,7 +621,6 @@ module ActionView
end
class DateTimeSelector #:nodoc:
- extend ActiveSupport::Memoizable
include ActionView::Helpers::TagHelper
DEFAULT_PREFIX = 'date'.freeze
@@ -786,11 +785,12 @@ module ActionView
# Returns translated month names, but also ensures that a custom month
# name array has a leading nil element.
def month_names
- month_names = @options[:use_month_names] || translated_month_names
- month_names.unshift(nil) if month_names.size < 13
- month_names
+ @month_names ||= begin
+ month_names = @options[:use_month_names] || translated_month_names
+ month_names.unshift(nil) if month_names.size < 13
+ month_names
+ end
end
- memoize :month_names
# Returns translated month names.
# => [nil, "January", "February", "March",
@@ -825,9 +825,8 @@ module ActionView
end
def date_order
- @options[:order] || translated_date_order
+ @date_order ||= @options[:order] || translated_date_order
end
- memoize :date_order
def translated_date_order
I18n.translate(:'date.order', :locale => @options[:locale]) || []
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index 6513edcf6e..3dc6d65432 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -128,6 +128,28 @@ module ActionView
# By default, <tt>post.person_id</tt> is the selected option. Specify <tt>:selected => value</tt> to use a different selection
# or <tt>:selected => nil</tt> to leave all options unselected. Similarly, you can specify values to be disabled in the option
# tags by specifying the <tt>:disabled</tt> option. This can either be a single value or an array of values to be disabled.
+ #
+ # ==== Gotcha
+ #
+ # The HTML specification says when +multiple+ parameter passed to select and all options got deselected
+ # web browsers do not send any value to server. Unfortunately this introduces a gotcha:
+ # if an +User+ model has many +roles+ and have +role_ids+ accessor, and in the form that edits roles of the user
+ # the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So,
+ # any mass-assignment idiom like
+ #
+ # @user.update_attributes(params[:user])
+ #
+ # wouldn't update roles.
+ #
+ # To prevent this the helper generates an auxiliary hidden field before
+ # every multiple select. The hidden field has the same name as multiple select and blank value.
+ #
+ # This way, the client either sends only the hidden field (representing
+ # the deselected multiple select box), or both fields. Since the HTML specification
+ # says key/value pairs have to be sent in the same order they appear in the
+ # form, and parameters extraction gets the last occurrence of any repeated
+ # key in the query string, that works for ordinary forms.
+ #
def select(object, method, choices, options = {}, html_options = {})
InstanceTag.new(object, method, self, options.delete(:object)).to_select_tag(choices, options, html_options)
end
@@ -552,43 +574,26 @@ module ActionView
include FormOptionsHelper
def to_select_tag(choices, options, html_options)
- html_options = html_options.stringify_keys
- add_default_name_and_id(html_options)
- value = value(object)
- selected_value = options.has_key?(:selected) ? options[:selected] : value
- disabled_value = options.has_key?(:disabled) ? options[:disabled] : nil
- content_tag("select", add_options(options_for_select(choices, :selected => selected_value, :disabled => disabled_value), options, selected_value), html_options)
+ selected_value = options.has_key?(:selected) ? options[:selected] : value(object)
+ select_content_tag(options_for_select(choices, :selected => selected_value, :disabled => options[:disabled]), options, html_options)
end
def to_collection_select_tag(collection, value_method, text_method, options, html_options)
- html_options = html_options.stringify_keys
- add_default_name_and_id(html_options)
- value = value(object)
- disabled_value = options.has_key?(:disabled) ? options[:disabled] : nil
- selected_value = options.has_key?(:selected) ? options[:selected] : value
- content_tag(
- "select", add_options(options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => disabled_value), options, value), html_options
+ selected_value = options.has_key?(:selected) ? options[:selected] : value(object)
+ select_content_tag(
+ options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => options[:disabled]), options, html_options
)
end
def to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
- html_options = html_options.stringify_keys
- add_default_name_and_id(html_options)
- value = value(object)
- content_tag(
- "select", add_options(option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value), options, value), html_options
+ select_content_tag(
+ option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value(object)), options, html_options
)
end
def to_time_zone_select_tag(priority_zones, options, html_options)
- html_options = html_options.stringify_keys
- add_default_name_and_id(html_options)
- value = value(object)
- content_tag("select",
- add_options(
- time_zone_options_for_select(value || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone),
- options, value
- ), html_options
+ select_content_tag(
+ time_zone_options_for_select(value(object) || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone), options, html_options
)
end
@@ -603,6 +608,17 @@ module ActionView
end
option_tags.html_safe
end
+
+ def select_content_tag(option_tags, options, html_options)
+ html_options = html_options.stringify_keys
+ add_default_name_and_id(html_options)
+ select = content_tag("select", add_options(option_tags, options, value(object)), html_options)
+ if html_options["multiple"]
+ tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select
+ else
+ select
+ end
+ end
end
class FormBuilder
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index a91e86f4db..72bc4510b5 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -597,6 +597,12 @@ module ActionView
number_field_tag(name, value, options.stringify_keys.update("type" => "range"))
end
+ # Creates the hidden UTF8 enforcer tag. Override this method in a helper
+ # to customize the tag.
+ def utf8_enforcer_tag
+ tag(:input, :type => "hidden", :name => "utf8", :value => "&#x2713;".html_safe)
+ end
+
private
def html_options_for_form(url_for_options, options, *parameters_for_url)
options.stringify_keys.tap do |html_options|
@@ -611,9 +617,6 @@ module ActionView
end
def extra_tags_for_form(html_options)
- snowman_tag = tag(:input, :type => "hidden",
- :name => "utf8", :value => "&#x2713;".html_safe)
-
authenticity_token = html_options.delete("authenticity_token")
method = html_options.delete("method").to_s
@@ -629,7 +632,7 @@ module ActionView
tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag(authenticity_token)
end
- tags = snowman_tag << method_tag
+ tags = utf8_enforcer_tag << method_tag
content_tag(:div, tags, :style => 'margin:0;padding:0;display:inline')
end
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 472b996a5e..4be95d8f7e 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -255,7 +255,7 @@ module ActionView
# simple_format("<span>I'm allowed!</span> It's true.", {}, :sanitize => false)
# # => "<p><span>I'm allowed!</span> It's true.</p>"
def simple_format(text, html_options={}, options={})
- text = ''.html_safe if text.nil?
+ text = '' if text.nil?
start_tag = tag('p', html_options, true)
text = sanitize(text) unless options[:sanitize] == false
text = text.to_str
@@ -263,7 +263,7 @@ module ActionView
text.gsub!(/\n\n+/, "</p>\n\n#{start_tag}") # 2+ newline -> paragraph
text.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
text.insert 0, start_tag
- text = ActiveSupport::SafeBuffer.new(text).safe_concat("</p>")
+ text.html_safe.safe_concat("</p>")
end
# Creates a Cycle object whose _to_s_ method cycles through elements of an
diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb
index fd8fe417d0..26b6e8b599 100644
--- a/actionpack/lib/action_view/helpers/translation_helper.rb
+++ b/actionpack/lib/action_view/helpers/translation_helper.rb
@@ -5,7 +5,7 @@ module I18n
class ExceptionHandler
include Module.new {
def call(exception, locale, key, options)
- exception.is_a?(MissingTranslation) ? super.html_safe : super
+ exception.is_a?(MissingTranslation) && options[:rescue_format] == :html ? super.html_safe : super
end
}
end
diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb
index 4b497d142d..38eb00ce01 100644
--- a/actionpack/lib/sprockets/railtie.rb
+++ b/actionpack/lib/sprockets/railtie.rb
@@ -54,12 +54,21 @@ module Sprockets
env = Sprockets::Environment.new(app.root.to_s)
env.static_root = File.join(app.root.join("public"), assets.prefix)
- env.paths.concat assets.paths
+
+ if env.respond_to?(:append_path)
+ assets.paths.each { |path| env.append_path(path) }
+ else
+ env.paths.concat assets.paths
+ end
env.logger = Rails.logger
- env.js_compressor = expand_js_compressor(assets.js_compressor)
- env.css_compressor = expand_css_compressor(assets.css_compressor)
+ if assets.compress
+ # temporarily hardcode default JS compressor to uglify. Soon, it will work
+ # the same as SCSS, where a default plugin sets the default.
+ env.js_compressor = expand_js_compressor(assets.js_compressor || :uglifier)
+ env.css_compressor = expand_css_compressor(assets.css_compressor)
+ end
env
end
diff --git a/actionpack/test/abstract/abstract_controller_test.rb b/actionpack/test/abstract/abstract_controller_test.rb
index 53712a60ec..5823e64637 100644
--- a/actionpack/test/abstract/abstract_controller_test.rb
+++ b/actionpack/test/abstract/abstract_controller_test.rb
@@ -181,7 +181,7 @@ module AbstractController
class TestLayouts < ActiveSupport::TestCase
test "layouts are included" do
controller = Me4.new
- result = controller.process(:index)
+ controller.process(:index)
assert_equal "Me4 Enter : Hello from me4/index.erb : Exit", controller.response_body
end
end
diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb
index f48b73b63a..6265e78030 100644
--- a/actionpack/test/controller/test_test.rb
+++ b/actionpack/test/controller/test_test.rb
@@ -612,7 +612,6 @@ XML
send(method, :test_remote_addr)
assert false, "expected RuntimeError, got nothing"
rescue RuntimeError => error
- assert true
assert_match(%r{@#{variable} is nil}, error.message)
rescue => error
assert false, "expected RuntimeError, got #{error.class}"
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index 3f3d6dcc2f..484e996f31 100644
--- a/actionpack/test/controller/url_for_test.rb
+++ b/actionpack/test/controller/url_for_test.rb
@@ -293,8 +293,8 @@ module AbstractController
first_class.default_url_options[:host] = first_host
second_class.default_url_options[:host] = second_host
- assert_equal first_class.default_url_options[:host], first_host
- assert_equal second_class.default_url_options[:host], second_host
+ assert_equal first_host, first_class.default_url_options[:host]
+ assert_equal second_host, second_class.default_url_options[:host]
end
def test_with_stringified_keys
diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb
index 621fb79915..ae8588cbb0 100644
--- a/actionpack/test/controller/webservice_test.rb
+++ b/actionpack/test/controller/webservice_test.rb
@@ -30,7 +30,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest
def test_check_parameters
with_test_route_set do
get "/"
- assert_blank @controller.response.body
+ assert_equal '', @controller.response.body
end
end
@@ -162,7 +162,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest
def test_use_xml_ximple_with_empty_request
with_test_route_set do
assert_nothing_raised { post "/", "", {'CONTENT_TYPE' => 'application/xml'} }
- assert_blank @controller.response.body
+ assert_equal '', @controller.response.body
end
end
diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb
index e2a7f1bad7..7e4a1519fb 100644
--- a/actionpack/test/dispatch/uploaded_file_test.rb
+++ b/actionpack/test/dispatch/uploaded_file_test.rb
@@ -12,6 +12,13 @@ module ActionDispatch
uf = Http::UploadedFile.new(:filename => 'foo', :tempfile => Object.new)
assert_equal 'foo', uf.original_filename
end
+
+ if "ruby".encoding_aware?
+ def test_filename_should_be_in_utf_8
+ uf = Http::UploadedFile.new(:filename => 'foo', :tempfile => Object.new)
+ assert_equal "UTF-8", uf.original_filename.encoding.to_s
+ end
+ end
def test_content_type
uf = Http::UploadedFile.new(:type => 'foo', :tempfile => Object.new)
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 5296556fe6..0507045ad2 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -1890,7 +1890,7 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
- def snowman(method = nil)
+ def hidden_fields(method = nil)
txt = %{<div style="margin:0;padding:0;display:inline">}
txt << %{<input name="utf8" type="hidden" value="&#x2713;" />}
if method && !method.to_s.in?(['get', 'post'])
@@ -1918,7 +1918,7 @@ class FormHelperTest < ActionView::TestCase
method = options
end
- form_text(action, id, html_class, remote, multipart, method) + snowman(method) + contents + "</form>"
+ form_text(action, id, html_class, remote, multipart, method) + hidden_fields(method) + contents + "</form>"
end
def test_default_form_builder
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index b92e1d9890..f3969895ae 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -457,6 +457,22 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_select_with_multiple_to_add_hidden_input
+ output_buffer = select(:post, :category, "", {}, :multiple => true)
+ assert_dom_equal(
+ "<input type=\"hidden\" name=\"post[category][]\" value=\"\"/><select multiple=\"multiple\" id=\"post_category\" name=\"post[category][]\"></select>",
+ output_buffer
+ )
+ end
+
+ def test_select_with_multiple_and_disabled_to_add_disabled_hidden_input
+ output_buffer = select(:post, :category, "", {}, :multiple => true, :disabled => true)
+ assert_dom_equal(
+ "<input disabled=\"disabled\"type=\"hidden\" name=\"post[category][]\" value=\"\"/><select multiple=\"multiple\" disabled=\"disabled\" id=\"post_category\" name=\"post[category][]\"></select>",
+ output_buffer
+ )
+ end
+
def test_select_with_blank
@post = Post.new
@post.category = "<mus>"
@@ -649,11 +665,11 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
- def test_collection_select_with_multiple_option_appends_array_brackets
+ def test_collection_select_with_multiple_option_appends_array_brackets_and_hidden_input
@post = Post.new
@post.author_name = "Babe"
- expected = "<select id=\"post_author_name\" name=\"post[author_name][]\" multiple=\"multiple\"><option value=\"\"></option>\n<option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>"
+ expected = "<input type=\"hidden\" name=\"post[author_name][]\" value=\"\"/><select id=\"post_author_name\" name=\"post[author_name][]\" multiple=\"multiple\"><option value=\"\"></option>\n<option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>"
# Should suffix default name with [].
assert_dom_equal expected, collection_select("post", "author_name", dummy_posts, "author_name", "author_name", { :include_blank => true }, :multiple => true)
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index f95308b847..979251bfd1 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -9,7 +9,7 @@ class FormTagHelperTest < ActionView::TestCase
@controller = BasicController.new
end
- def snowman(options = {})
+ def hidden_fields(options = {})
method = options[:method]
txt = %{<div style="margin:0;padding:0;display:inline">}
@@ -34,7 +34,7 @@ class FormTagHelperTest < ActionView::TestCase
end
def whole_form(action = "http://www.example.com", options = {})
- out = form_text(action, options) + snowman(options)
+ out = form_text(action, options) + hidden_fields(options)
if block_given?
out << yield << "</form>"
diff --git a/actionpack/test/template/translation_helper_test.rb b/actionpack/test/template/translation_helper_test.rb
index 9b5c6d127c..cd9f54e04c 100644
--- a/actionpack/test/template/translation_helper_test.rb
+++ b/actionpack/test/template/translation_helper_test.rb
@@ -38,11 +38,19 @@ class TranslationHelperTest < ActiveSupport::TestCase
def test_returns_missing_translation_message_wrapped_into_span
expected = '<span class="translation_missing" title="translation missing: en.translations.missing">Missing</span>'
assert_equal expected, translate(:"translations.missing")
+ assert_equal true, translate(:"translations.missing").html_safe?
end
def test_returns_missing_translation_message_using_nil_as_rescue_format
expected = 'translation missing: en.translations.missing'
assert_equal expected, translate(:"translations.missing", :rescue_format => nil)
+ assert_equal false, translate(:"translations.missing", :rescue_format => nil).html_safe?
+ end
+
+ def test_i18n_translate_defaults_to_nil_rescue_format
+ expected = 'translation missing: en.translations.missing'
+ assert_equal expected, I18n.translate(:"translations.missing")
+ assert_equal false, I18n.translate(:"translations.missing").html_safe?
end
def test_translation_returning_an_array
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index 4aa45c8bf0..1e54f0174c 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -15,6 +15,7 @@ class UrlHelperTest < ActiveSupport::TestCase
routes.draw do
match "/" => "foo#bar"
match "/other" => "foo#other"
+ match "/article/:id" => "foo#article", :as => :article
end
include routes.url_helpers
@@ -264,6 +265,13 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_equal '<a href="/">Example site</a>', out
end
+ def test_link_tag_with_html_safe_string
+ assert_dom_equal(
+ "<a href=\"/article/Gerd_M%C3%BCller\">Gerd Müller</a>",
+ link_to("Gerd Müller", article_path("Gerd_Müller".html_safe))
+ )
+ end
+
def test_link_to_unless
assert_equal "Showing", link_to_unless(true, "Showing", url_hash)
diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG
index b1ad315c46..c38349b95e 100644
--- a/activemodel/CHANGELOG
+++ b/activemodel/CHANGELOG
@@ -2,6 +2,9 @@
*Rails 3.1.0 (unreleased)*
+* Alternate I18n namespace lookup is no longer supported.
+ Instead of "activerecord.models.admins.post", do "activerecord.models.admins/post" instead [José Valim]
+
* attr_accessible and friends now accepts :as as option to specify a role [Josh Kalderimis]
* Add support for proc or lambda as an option for InclusionValidator,
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index c7b9c41f46..4c1a82f413 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -21,7 +21,7 @@ module ActiveModel
@partial_path = "#{@collection}/#{@element}".freeze
@param_key = (namespace ? _singularize(@unnamespaced) : @singular).freeze
@route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural).freeze
- @i18n_key = self.underscore.tr('/', '.').to_sym
+ @i18n_key = self.underscore.to_sym
end
# Transform the model name into a more humane format, using I18n. By default,
@@ -35,9 +35,8 @@ module ActiveModel
@klass.respond_to?(:i18n_scope)
defaults = @klass.lookup_ancestors.map do |klass|
- [klass.model_name.i18n_key,
- klass.model_name.i18n_key.to_s.tr('.', '/').to_sym]
- end.flatten
+ klass.model_name.i18n_key
+ end
defaults << options[:default] if options[:default]
defaults << @human
diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb
index c615311692..6d64c81b5f 100644
--- a/activemodel/lib/active_model/translation.rb
+++ b/activemodel/lib/active_model/translation.rb
@@ -44,9 +44,8 @@ module ActiveModel
# Specify +options+ with additional translating options.
def human_attribute_name(attribute, options = {})
defaults = lookup_ancestors.map do |klass|
- [:"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}.#{attribute}",
- :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key.to_s.tr('.', '/')}.#{attribute}"]
- end.flatten
+ :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}.#{attribute}"
+ end
defaults << :"attributes.#{attribute}"
defaults << options.delete(:default) if options[:default]
diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb
index 022c6716bd..9840e3364c 100644
--- a/activemodel/test/cases/attribute_methods_test.rb
+++ b/activemodel/test/cases/attribute_methods_test.rb
@@ -78,7 +78,6 @@ class AttributeMethodsTest < ActiveModel::TestCase
test '#define_attribute_method generates attribute method with invalid identifier characters' do
ModelWithWeirdNamesAttributes.define_attribute_method(:'a?b')
- ModelWithWeirdNamesAttributes.define_attribute_method(:'a?b')
assert_respond_to ModelWithWeirdNamesAttributes.new, :'a?b'
assert_equal "value of a?b", ModelWithWeirdNamesAttributes.new.send('a?b')
diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb
index 838956bc98..1b1d972d5c 100644
--- a/activemodel/test/cases/translation_test.rb
+++ b/activemodel/test/cases/translation_test.rb
@@ -76,15 +76,5 @@ class ActiveModelI18nTests < ActiveModel::TestCase
Person.model_name.human(options)
assert_equal({:default => 'person model'}, options)
end
-
- def test_alternate_namespaced_model_attribute_translation
- I18n.backend.store_translations 'en', :activemodel => {:attributes => {:person => {:gender => {:attribute => 'person gender attribute'}}}}
- assert_equal 'person gender attribute', Person::Gender.human_attribute_name('attribute')
- end
-
- def test_alternate_namespaced_model_translation
- I18n.backend.store_translations 'en', :activemodel => {:models => {:person => {:gender => 'person gender model'}}}
- assert_equal 'person gender model', Person::Gender.model_name.human
- end
end
diff --git a/activerecord/RUNNING_UNIT_TESTS b/activerecord/RUNNING_UNIT_TESTS
index 8fe9a357b4..6a2e23b01f 100644
--- a/activerecord/RUNNING_UNIT_TESTS
+++ b/activerecord/RUNNING_UNIT_TESTS
@@ -32,3 +32,8 @@ By default the tests run with the Identity Map turned off. But all tests should
not the identity map is on or off. You can turn it on using the IM env variable:
$ IM=true ruby -Itest test/case/base_test.rb
+
+== Config file
+
+By default, the config file is expected to be at the path test/config.yml. You can specify a
+custom location with the ARCONFIG environment variable.
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 7e1a41e84d..337a0d48f1 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -114,19 +114,13 @@ module ActiveRecord
# Add +records+ to this association. Returns +self+ so method calls may be chained.
# Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
def concat(*records)
- result = true
load_target if owner.new_record?
- transaction do
- records.flatten.each do |record|
- raise_on_type_mismatch(record)
- add_to_target(record) do |r|
- result &&= insert_record(record) unless owner.new_record?
- end
- end
+ if owner.new_record?
+ concat_records(records)
+ else
+ transaction { concat_records(records) }
end
-
- result && records
end
# Starts a transaction in the association class's database connection.
@@ -295,14 +289,10 @@ module ActiveRecord
other_array.each { |val| raise_on_type_mismatch(val) }
original_target = load_target.dup
- transaction do
- delete(target - other_array)
-
- unless concat(other_array - target)
- @target = original_target
- raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
- "new records could not be saved."
- end
+ if owner.new_record?
+ replace_records(other_array, original_target)
+ else
+ transaction { replace_records(other_array, original_target) }
end
end
@@ -444,14 +434,20 @@ module ActiveRecord
records.each { |record| raise_on_type_mismatch(record) }
existing_records = records.reject { |r| r.new_record? }
- transaction do
- records.each { |record| callback(:before_remove, record) }
+ if existing_records.empty?
+ remove_records(existing_records, records, method)
+ else
+ transaction { remove_records(existing_records, records, method) }
+ end
+ end
- delete_records(existing_records, method) if existing_records.any?
- records.each { |record| target.delete(record) }
+ def remove_records(existing_records, records, method)
+ records.each { |record| callback(:before_remove, record) }
- records.each { |record| callback(:after_remove, record) }
- end
+ delete_records(existing_records, method) if existing_records.any?
+ records.each { |record| target.delete(record) }
+
+ records.each { |record| callback(:after_remove, record) }
end
# Delete the given records from the association, using one of the methods :destroy,
@@ -460,6 +456,29 @@ module ActiveRecord
raise NotImplementedError
end
+ def replace_records(new_target, original_target)
+ delete(target - new_target)
+
+ unless concat(new_target - target)
+ @target = original_target
+ raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
+ "new records could not be saved."
+ end
+ end
+
+ def concat_records(records)
+ result = true
+
+ records.flatten.each do |record|
+ raise_on_type_mismatch(record)
+ add_to_target(record) do |r|
+ result &&= insert_record(record) unless owner.new_record?
+ end
+ end
+
+ result && records
+ end
+
def callback(method, record)
callbacks_for(method).each do |callback|
case callback
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 2f283ff6bc..6a5d282973 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -428,10 +428,6 @@ module ActiveRecord #:nodoc:
class_attribute :default_scopes, :instance_writer => false
self.default_scopes = []
- # Boolean flag to prevent infinite recursion when evaluating default scopes
- class_attribute :apply_default_scope, :instance_writer => false
- self.apply_default_scope = true
-
# Returns a hash of all the attributes that have been specified for serialization as
# keys and their class restriction as values.
class_attribute :serialized_attributes
@@ -1265,11 +1261,11 @@ MSG
self.default_scopes = default_scopes + [scope]
end
- # The apply_default_scope flag is used to prevent an infinite recursion situation where
+ # The @ignore_default_scope flag is used to prevent an infinite recursion situation where
# a default scope references a scope which has a default scope which references a scope...
def build_default_scope #:nodoc:
- return unless apply_default_scope
- self.apply_default_scope = false
+ return if defined?(@ignore_default_scope) && @ignore_default_scope
+ @ignore_default_scope = true
if method(:default_scope).owner != Base.singleton_class
default_scope
@@ -1285,7 +1281,7 @@ MSG
end
end
ensure
- self.apply_default_scope = true
+ @ignore_default_scope = false
end
# Returns the class type of the record using the current module as a prefix. So descendants of
@@ -1717,10 +1713,13 @@ MSG
attributes.each do |k, v|
if k.include?("(")
multi_parameter_attributes << [ k, v ]
- elsif respond_to?("#{k}=")
- send("#{k}=", v)
else
- raise(UnknownAttributeError, "unknown attribute: #{k}")
+ method_name = "#{k}="
+ if respond_to?(method_name)
+ method(method_name).arity == -2 ? send(method_name, v, options) : send(method_name, v)
+ else
+ raise(UnknownAttributeError, "unknown attribute: #{k}")
+ end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 8ffd40f7e5..dd1d2d4fba 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -119,6 +119,7 @@ module ActiveRecord
with_connection do |conn|
conn.tables.each { |table| @tables[table] = true }
+ @tables[name] = true if !@tables.key?(name) && conn.table_exists?(name)
end
@tables.key? name
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 24d8c8cad2..d6c167ad36 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-gem 'mysql2', '~> 0.3.0'
+gem 'mysql2', '~> 0.3.6'
require 'mysql2'
module ActiveRecord
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 08b27b6a8e..f51fd21077 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -276,15 +276,15 @@ module ActiveRecord
type = (reflection.collection? ? :collection : :one_to_one)
- # def pirate_attributes=(attributes)
- # assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
+ # def pirate_attributes=(attributes, assignment_opts = {})
+ # assign_nested_attributes_for_one_to_one_association(:pirate, attributes, assignment_opts)
# end
class_eval <<-eoruby, __FILE__, __LINE__ + 1
if method_defined?(:#{association_name}_attributes=)
remove_method(:#{association_name}_attributes=)
end
- def #{association_name}_attributes=(attributes)
- assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
+ def #{association_name}_attributes=(attributes, assignment_opts = {})
+ assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, assignment_opts)
end
eoruby
else
@@ -319,21 +319,21 @@ module ActiveRecord
# If the given attributes include a matching <tt>:id</tt> attribute, or
# update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
# then the existing record will be marked for destruction.
- def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
+ def assign_nested_attributes_for_one_to_one_association(association_name, attributes, assignment_opts = {})
options = self.nested_attributes_options[association_name]
attributes = attributes.with_indifferent_access
if (options[:update_only] || !attributes['id'].blank?) && (record = send(association_name)) &&
(options[:update_only] || record.id.to_s == attributes['id'].to_s)
- assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)
+ assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy], assignment_opts) unless call_reject_if(association_name, attributes)
- elsif attributes['id'].present?
+ elsif attributes['id'].present? && !assignment_opts[:without_protection]
raise_nested_attributes_record_not_found(association_name, attributes['id'])
elsif !reject_new_record?(association_name, attributes)
method = "build_#{association_name}"
if respond_to?(method)
- send(method, attributes.except(*UNASSIGNABLE_KEYS))
+ send(method, attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
else
raise ArgumentError, "Cannot build association #{association_name}. Are you trying to build a polymorphic one-to-one association?"
end
@@ -367,7 +367,7 @@ module ActiveRecord
# { :name => 'John' },
# { :id => '2', :_destroy => true }
# ])
- def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
+ def assign_nested_attributes_for_collection_association(association_name, attributes_collection, assignment_opts = {})
options = self.nested_attributes_options[association_name]
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
@@ -401,7 +401,7 @@ module ActiveRecord
if attributes['id'].blank?
unless reject_new_record?(association_name, attributes)
- association.build(attributes.except(*UNASSIGNABLE_KEYS))
+ association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
end
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
unless association.loaded? || call_reject_if(association_name, attributes)
@@ -418,8 +418,10 @@ module ActiveRecord
end
if !call_reject_if(association_name, attributes)
- assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
+ assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy], assignment_opts)
end
+ elsif assignment_opts[:without_protection]
+ association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
else
raise_nested_attributes_record_not_found(association_name, attributes['id'])
end
@@ -428,8 +430,8 @@ module ActiveRecord
# Updates a record with the +attributes+ or marks it for destruction if
# +allow_destroy+ is +true+ and has_destroy_flag? returns +true+.
- def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
- record.attributes = attributes.except(*UNASSIGNABLE_KEYS)
+ def assign_to_or_mark_for_destruction(record, attributes, allow_destroy, assignment_opts)
+ record.assign_attributes(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
end
@@ -458,5 +460,9 @@ module ActiveRecord
def raise_nested_attributes_record_not_found(association_name, record_id)
raise RecordNotFound, "Couldn't find #{self.class.reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
end
+
+ def unassignable_keys(assignment_opts)
+ assignment_opts[:without_protection] ? UNASSIGNABLE_KEYS - %w[id] : UNASSIGNABLE_KEYS
+ end
end
end
diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb
index c61428e104..ffe9b08dce 100644
--- a/activerecord/lib/active_record/test_case.rb
+++ b/activerecord/lib/active_record/test_case.rb
@@ -31,27 +31,30 @@ module ActiveRecord
end
def assert_sql(*patterns_to_match)
- $queries_executed = []
+ ActiveRecord::SQLCounter.log = []
yield
- $queries_executed
+ ActiveRecord::SQLCounter.log
ensure
failed_patterns = []
patterns_to_match.each do |pattern|
- failed_patterns << pattern unless $queries_executed.any?{ |sql| pattern === sql }
+ failed_patterns << pattern unless ActiveRecord::SQLCounter.log.any?{ |sql| pattern === sql }
end
- assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{$queries_executed.size == 0 ? '' : "\nQueries:\n#{$queries_executed.join("\n")}"}"
+ assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{ActiveRecord::SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{ActiveRecord::SQLCounter.log.join("\n")}"}"
end
def assert_queries(num = 1)
- $queries_executed = []
+ ActiveRecord::SQLCounter.log = []
yield
ensure
- %w{ BEGIN COMMIT }.each { |x| $queries_executed.delete(x) }
- assert_equal num, $queries_executed.size, "#{$queries_executed.size} instead of #{num} queries were executed.#{$queries_executed.size == 0 ? '' : "\nQueries:\n#{$queries_executed.join("\n")}"}"
+ assert_equal num, ActiveRecord::SQLCounter.log.size, "#{ActiveRecord::SQLCounter.log.size} instead of #{num} queries were executed.#{ActiveRecord::SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{ActiveRecord::SQLCounter.log.join("\n")}"}"
end
def assert_no_queries(&block)
+ prev_ignored_sql = ActiveRecord::SQLCounter.ignored_sql
+ ActiveRecord::SQLCounter.ignored_sql = []
assert_queries(0, &block)
+ ensure
+ ActiveRecord::SQLCounter.ignored_sql = prev_ignored_sql
end
def with_kcode(kcode)
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 49999630b6..9af1f7249f 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -537,6 +537,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 3, companies(:first_firm).clients_of_firm(true).size
end
+ def test_transactions_when_adding_to_persisted
+ good = Client.new(:name => "Good")
+ bad = Client.new(:name => "Bad", :raise_on_save => true)
+
+ begin
+ companies(:first_firm).clients_of_firm.concat(good, bad)
+ rescue Client::RaisedOnSave
+ end
+
+ assert !companies(:first_firm).clients_of_firm(true).include?(good)
+ end
+
+ def test_transactions_when_adding_to_new_record
+ assert_no_queries do
+ firm = Firm.new
+ firm.clients_of_firm.concat(Client.new("name" => "Natural Company"))
+ end
+ end
+
def test_new_aliased_to_build
company = companies(:first_firm)
new_client = assert_no_queries { company.clients_of_firm.new("name" => "Another Client") }
@@ -778,6 +797,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 0, companies(:first_firm).clients_of_firm(true).size
end
+ def test_transaction_when_deleting_persisted
+ good = Client.new(:name => "Good")
+ bad = Client.new(:name => "Bad", :raise_on_destroy => true)
+
+ companies(:first_firm).clients_of_firm = [good, bad]
+
+ begin
+ companies(:first_firm).clients_of_firm.destroy(good, bad)
+ rescue Client::RaisedOnDestroy
+ end
+
+ assert_equal [good, bad], companies(:first_firm).clients_of_firm(true)
+ end
+
+ def test_transaction_when_deleting_new_record
+ assert_no_queries do
+ firm = Firm.new
+ client = Client.new("name" => "New Client")
+ firm.clients_of_firm << client
+ firm.clients_of_firm.destroy(client)
+ end
+ end
+
def test_clearing_an_association_collection
firm = companies(:first_firm)
client_id = firm.clients_of_firm.first.id
@@ -1111,6 +1153,27 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal orig_accounts, firm.accounts
end
+ def test_transactions_when_replacing_on_persisted
+ good = Client.new(:name => "Good")
+ bad = Client.new(:name => "Bad", :raise_on_save => true)
+
+ companies(:first_firm).clients_of_firm = [good]
+
+ begin
+ companies(:first_firm).clients_of_firm = [bad]
+ rescue Client::RaisedOnSave
+ end
+
+ assert_equal [good], companies(:first_firm).clients_of_firm(true)
+ end
+
+ def test_transactions_when_replacing_on_new_record
+ assert_no_queries do
+ firm = Firm.new
+ firm.clients_of_firm = [Client.new("name" => "New Client")]
+ end
+ end
+
def test_get_ids
assert_equal [companies(:first_client).id, companies(:second_client).id], companies(:first_firm).client_ids
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 8f55b7ebe6..4ad2cdfc7e 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -837,7 +837,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
@pirate.parrots.each { |parrot| parrot.mark_for_destruction }
assert @pirate.save
- assert_no_queries do
+ assert_queries(0) do
assert @pirate.save
end
end
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index d0dc9cb03d..6735bc521b 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -58,15 +58,15 @@ end
module ActiveRecord
class SQLCounter
- IGNORED_SQL = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/]
+ cattr_accessor :ignored_sql
+ self.ignored_sql = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/]
# FIXME: this needs to be refactored so specific database can add their own
# ignored SQL. This ignored SQL is for Oracle.
- IGNORED_SQL.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im]
+ ignored_sql.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im]
- def initialize
- $queries_executed = []
- end
+ cattr_accessor :log
+ self.log = []
def call(name, start, finish, message_id, values)
sql = values[:sql]
@@ -74,10 +74,11 @@ module ActiveRecord
# FIXME: this seems bad. we should probably have a better way to indicate
# the query was cached
unless 'CACHE' == values[:name]
- $queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
+ self.class.log << sql unless self.class.ignored_sql.any? { |r| sql =~ r }
end
end
end
+
ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)
end
diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb
index 33737e12a8..ef35f3341e 100644
--- a/activerecord/test/cases/mass_assignment_security_test.rb
+++ b/activerecord/test/cases/mass_assignment_security_test.rb
@@ -384,81 +384,81 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase
# build
- def test_has_one_build_with_attr_protected_attributes
+ def test_belongs_to_build_with_attr_protected_attributes
best_friend = @person.build_best_friend_of(attributes_hash)
assert_default_attributes(best_friend)
end
- def test_has_one_build_with_attr_accessible_attributes
+ def test_belongs_to_build_with_attr_accessible_attributes
best_friend = @person.build_best_friend_of(attributes_hash)
assert_default_attributes(best_friend)
end
- def test_has_one_build_with_admin_role_with_attr_protected_attributes
+ def test_belongs_to_build_with_admin_role_with_attr_protected_attributes
best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
- def test_has_one_build_with_admin_role_with_attr_accessible_attributes
+ def test_belongs_to_build_with_admin_role_with_attr_accessible_attributes
best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
- def test_has_one_build_without_protection
+ def test_belongs_to_build_without_protection
best_friend = @person.build_best_friend_of(attributes_hash, :without_protection => true)
assert_all_attributes(best_friend)
end
# create
- def test_has_one_create_with_attr_protected_attributes
+ def test_belongs_to_create_with_attr_protected_attributes
best_friend = @person.create_best_friend_of(attributes_hash)
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_attr_accessible_attributes
+ def test_belongs_to_create_with_attr_accessible_attributes
best_friend = @person.create_best_friend_of(attributes_hash)
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_role_with_attr_protected_attributes
+ def test_belongs_to_create_with_admin_role_with_attr_protected_attributes
best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_role_with_attr_accessible_attributes
+ def test_belongs_to_create_with_admin_role_with_attr_accessible_attributes
best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_without_protection
+ def test_belongs_to_create_without_protection
best_friend = @person.create_best_friend_of(attributes_hash, :without_protection => true)
assert_all_attributes(best_friend)
end
# create!
- def test_has_one_create_with_bang_with_attr_protected_attributes
+ def test_belongs_to_create_with_bang_with_attr_protected_attributes
best_friend = @person.create_best_friend!(attributes_hash)
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_attr_accessible_attributes
+ def test_belongs_to_create_with_bang_with_attr_accessible_attributes
best_friend = @person.create_best_friend!(attributes_hash)
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes
+ def test_belongs_to_create_with_bang_with_admin_role_with_attr_protected_attributes
best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes
+ def test_belongs_to_create_with_bang_with_admin_role_with_attr_accessible_attributes
best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_without_protection
+ def test_belongs_to_create_with_bang_without_protection
best_friend = @person.create_best_friend!(attributes_hash, :without_protection => true)
assert_all_attributes(best_friend)
end
@@ -472,83 +472,328 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase
# build
- def test_has_one_build_with_attr_protected_attributes
+ def test_has_many_build_with_attr_protected_attributes
best_friend = @person.best_friends.build(attributes_hash)
assert_default_attributes(best_friend)
end
- def test_has_one_build_with_attr_accessible_attributes
+ def test_has_many_build_with_attr_accessible_attributes
best_friend = @person.best_friends.build(attributes_hash)
assert_default_attributes(best_friend)
end
- def test_has_one_build_with_admin_role_with_attr_protected_attributes
+ def test_has_many_build_with_admin_role_with_attr_protected_attributes
best_friend = @person.best_friends.build(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
- def test_has_one_build_with_admin_role_with_attr_accessible_attributes
+ def test_has_many_build_with_admin_role_with_attr_accessible_attributes
best_friend = @person.best_friends.build(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
- def test_has_one_build_without_protection
+ def test_has_many_build_without_protection
best_friend = @person.best_friends.build(attributes_hash, :without_protection => true)
assert_all_attributes(best_friend)
end
# create
- def test_has_one_create_with_attr_protected_attributes
+ def test_has_many_create_with_attr_protected_attributes
best_friend = @person.best_friends.create(attributes_hash)
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_attr_accessible_attributes
+ def test_has_many_create_with_attr_accessible_attributes
best_friend = @person.best_friends.create(attributes_hash)
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_role_with_attr_protected_attributes
+ def test_has_many_create_with_admin_role_with_attr_protected_attributes
best_friend = @person.best_friends.create(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_role_with_attr_accessible_attributes
+ def test_has_many_create_with_admin_role_with_attr_accessible_attributes
best_friend = @person.best_friends.create(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_without_protection
+ def test_has_many_create_without_protection
best_friend = @person.best_friends.create(attributes_hash, :without_protection => true)
assert_all_attributes(best_friend)
end
# create!
- def test_has_one_create_with_bang_with_attr_protected_attributes
+ def test_has_many_create_with_bang_with_attr_protected_attributes
best_friend = @person.best_friends.create!(attributes_hash)
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_attr_accessible_attributes
+ def test_has_many_create_with_bang_with_attr_accessible_attributes
best_friend = @person.best_friends.create!(attributes_hash)
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes
+ def test_has_many_create_with_bang_with_admin_role_with_attr_protected_attributes
best_friend = @person.best_friends.create!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes
+ def test_has_many_create_with_bang_with_admin_role_with_attr_accessible_attributes
best_friend = @person.best_friends.create!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_without_protection
+ def test_has_many_create_with_bang_without_protection
best_friend = @person.best_friends.create!(attributes_hash, :without_protection => true)
assert_all_attributes(best_friend)
end
end
+
+
+class MassAssignmentSecurityNestedAttributesTest < ActiveRecord::TestCase
+ include MassAssignmentTestHelpers
+
+ def nested_attributes_hash(association, collection = false, except = [:id])
+ if collection
+ { :first_name => 'David' }.merge(:"#{association}_attributes" => [attributes_hash.except(*except)])
+ else
+ { :first_name => 'David' }.merge(:"#{association}_attributes" => attributes_hash.except(*except))
+ end
+ end
+
+ # build
+
+ def test_has_one_new_with_attr_protected_attributes
+ person = LoosePerson.new(nested_attributes_hash(:best_friend))
+ assert_default_attributes(person.best_friend)
+ end
+
+ def test_has_one_new_with_attr_accessible_attributes
+ person = TightPerson.new(nested_attributes_hash(:best_friend))
+ assert_default_attributes(person.best_friend)
+ end
+
+ def test_has_one_new_with_admin_role_with_attr_protected_attributes
+ person = LoosePerson.new(nested_attributes_hash(:best_friend), :as => :admin)
+ assert_admin_attributes(person.best_friend)
+ end
+
+ def test_has_one_new_with_admin_role_with_attr_accessible_attributes
+ person = TightPerson.new(nested_attributes_hash(:best_friend), :as => :admin)
+ assert_admin_attributes(person.best_friend)
+ end
+
+ def test_has_one_new_without_protection
+ person = LoosePerson.new(nested_attributes_hash(:best_friend, false, nil), :without_protection => true)
+ assert_all_attributes(person.best_friend)
+ end
+
+ def test_belongs_to_new_with_attr_protected_attributes
+ person = LoosePerson.new(nested_attributes_hash(:best_friend_of))
+ assert_default_attributes(person.best_friend_of)
+ end
+
+ def test_belongs_to_new_with_attr_accessible_attributes
+ person = TightPerson.new(nested_attributes_hash(:best_friend_of))
+ assert_default_attributes(person.best_friend_of)
+ end
+
+ def test_belongs_to_new_with_admin_role_with_attr_protected_attributes
+ person = LoosePerson.new(nested_attributes_hash(:best_friend_of), :as => :admin)
+ assert_admin_attributes(person.best_friend_of)
+ end
+
+ def test_belongs_to_new_with_admin_role_with_attr_accessible_attributes
+ person = TightPerson.new(nested_attributes_hash(:best_friend_of), :as => :admin)
+ assert_admin_attributes(person.best_friend_of)
+ end
+
+ def test_belongs_to_new_without_protection
+ person = LoosePerson.new(nested_attributes_hash(:best_friend_of, false, nil), :without_protection => true)
+ assert_all_attributes(person.best_friend_of)
+ end
+
+ def test_has_many_new_with_attr_protected_attributes
+ person = LoosePerson.new(nested_attributes_hash(:best_friends, true))
+ assert_default_attributes(person.best_friends.first)
+ end
+
+ def test_has_many_new_with_attr_accessible_attributes
+ person = TightPerson.new(nested_attributes_hash(:best_friends, true))
+ assert_default_attributes(person.best_friends.first)
+ end
+
+ def test_has_many_new_with_admin_role_with_attr_protected_attributes
+ person = LoosePerson.new(nested_attributes_hash(:best_friends, true), :as => :admin)
+ assert_admin_attributes(person.best_friends.first)
+ end
+
+ def test_has_many_new_with_admin_role_with_attr_accessible_attributes
+ person = TightPerson.new(nested_attributes_hash(:best_friends, true), :as => :admin)
+ assert_admin_attributes(person.best_friends.first)
+ end
+
+ def test_has_many_new_without_protection
+ person = LoosePerson.new(nested_attributes_hash(:best_friends, true, nil), :without_protection => true)
+ assert_all_attributes(person.best_friends.first)
+ end
+
+ # create
+
+ def test_has_one_create_with_attr_protected_attributes
+ person = LoosePerson.create(nested_attributes_hash(:best_friend))
+ assert_default_attributes(person.best_friend, true)
+ end
+
+ def test_has_one_create_with_attr_accessible_attributes
+ person = TightPerson.create(nested_attributes_hash(:best_friend))
+ assert_default_attributes(person.best_friend, true)
+ end
+
+ def test_has_one_create_with_admin_role_with_attr_protected_attributes
+ person = LoosePerson.create(nested_attributes_hash(:best_friend), :as => :admin)
+ assert_admin_attributes(person.best_friend, true)
+ end
+
+ def test_has_one_create_with_admin_role_with_attr_accessible_attributes
+ person = TightPerson.create(nested_attributes_hash(:best_friend), :as => :admin)
+ assert_admin_attributes(person.best_friend, true)
+ end
+
+ def test_has_one_create_without_protection
+ person = LoosePerson.create(nested_attributes_hash(:best_friend, false, nil), :without_protection => true)
+ assert_all_attributes(person.best_friend)
+ end
+
+ def test_belongs_to_create_with_attr_protected_attributes
+ person = LoosePerson.create(nested_attributes_hash(:best_friend_of))
+ assert_default_attributes(person.best_friend_of, true)
+ end
+
+ def test_belongs_to_create_with_attr_accessible_attributes
+ person = TightPerson.create(nested_attributes_hash(:best_friend_of))
+ assert_default_attributes(person.best_friend_of, true)
+ end
+
+ def test_belongs_to_create_with_admin_role_with_attr_protected_attributes
+ person = LoosePerson.create(nested_attributes_hash(:best_friend_of), :as => :admin)
+ assert_admin_attributes(person.best_friend_of, true)
+ end
+
+ def test_belongs_to_create_with_admin_role_with_attr_accessible_attributes
+ person = TightPerson.create(nested_attributes_hash(:best_friend_of), :as => :admin)
+ assert_admin_attributes(person.best_friend_of, true)
+ end
+
+ def test_belongs_to_create_without_protection
+ person = LoosePerson.create(nested_attributes_hash(:best_friend_of, false, nil), :without_protection => true)
+ assert_all_attributes(person.best_friend_of)
+ end
+
+ def test_has_many_create_with_attr_protected_attributes
+ person = LoosePerson.create(nested_attributes_hash(:best_friends, true))
+ assert_default_attributes(person.best_friends.first, true)
+ end
+
+ def test_has_many_create_with_attr_accessible_attributes
+ person = TightPerson.create(nested_attributes_hash(:best_friends, true))
+ assert_default_attributes(person.best_friends.first, true)
+ end
+
+ def test_has_many_create_with_admin_role_with_attr_protected_attributes
+ person = LoosePerson.create(nested_attributes_hash(:best_friends, true), :as => :admin)
+ assert_admin_attributes(person.best_friends.first, true)
+ end
+
+ def test_has_many_create_with_admin_role_with_attr_accessible_attributes
+ person = TightPerson.create(nested_attributes_hash(:best_friends, true), :as => :admin)
+ assert_admin_attributes(person.best_friends.first, true)
+ end
+
+ def test_has_many_create_without_protection
+ person = LoosePerson.create(nested_attributes_hash(:best_friends, true, nil), :without_protection => true)
+ assert_all_attributes(person.best_friends.first)
+ end
+
+ # create!
+
+ def test_has_one_create_with_bang_with_attr_protected_attributes
+ person = LoosePerson.create!(nested_attributes_hash(:best_friend))
+ assert_default_attributes(person.best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_attr_accessible_attributes
+ person = TightPerson.create!(nested_attributes_hash(:best_friend))
+ assert_default_attributes(person.best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes
+ person = LoosePerson.create!(nested_attributes_hash(:best_friend), :as => :admin)
+ assert_admin_attributes(person.best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes
+ person = TightPerson.create!(nested_attributes_hash(:best_friend), :as => :admin)
+ assert_admin_attributes(person.best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_without_protection
+ person = LoosePerson.create!(nested_attributes_hash(:best_friend, false, nil), :without_protection => true)
+ assert_all_attributes(person.best_friend)
+ end
+
+ def test_belongs_to_create_with_bang_with_attr_protected_attributes
+ person = LoosePerson.create!(nested_attributes_hash(:best_friend_of))
+ assert_default_attributes(person.best_friend_of, true)
+ end
+
+ def test_belongs_to_create_with_bang_with_attr_accessible_attributes
+ person = TightPerson.create!(nested_attributes_hash(:best_friend_of))
+ assert_default_attributes(person.best_friend_of, true)
+ end
+
+ def test_belongs_to_create_with_bang_with_admin_role_with_attr_protected_attributes
+ person = LoosePerson.create!(nested_attributes_hash(:best_friend_of), :as => :admin)
+ assert_admin_attributes(person.best_friend_of, true)
+ end
+
+ def test_belongs_to_create_with_bang_with_admin_role_with_attr_accessible_attributes
+ person = TightPerson.create!(nested_attributes_hash(:best_friend_of), :as => :admin)
+ assert_admin_attributes(person.best_friend_of, true)
+ end
+
+ def test_belongs_to_create_with_bang_without_protection
+ person = LoosePerson.create!(nested_attributes_hash(:best_friend_of, false, nil), :without_protection => true)
+ assert_all_attributes(person.best_friend_of)
+ end
+
+ def test_has_many_create_with_bang_with_attr_protected_attributes
+ person = LoosePerson.create!(nested_attributes_hash(:best_friends, true))
+ assert_default_attributes(person.best_friends.first, true)
+ end
+
+ def test_has_many_create_with_bang_with_attr_accessible_attributes
+ person = TightPerson.create!(nested_attributes_hash(:best_friends, true))
+ assert_default_attributes(person.best_friends.first, true)
+ end
+
+ def test_has_many_create_with_bang_with_admin_role_with_attr_protected_attributes
+ person = LoosePerson.create!(nested_attributes_hash(:best_friends, true), :as => :admin)
+ assert_admin_attributes(person.best_friends.first, true)
+ end
+
+ def test_has_many_create_with_bang_with_admin_role_with_attr_accessible_attributes
+ person = TightPerson.create!(nested_attributes_hash(:best_friends, true), :as => :admin)
+ assert_admin_attributes(person.best_friends.first, true)
+ end
+
+ def test_has_many_create_with_bang_without_protection
+ person = LoosePerson.create!(nested_attributes_hash(:best_friends, true, nil), :without_protection => true)
+ assert_all_attributes(person.best_friends.first)
+ end
+
+end
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index e0b30efd51..c1f7a4171a 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -124,6 +124,18 @@ class Client < Company
has_many :accounts, :through => :firm
belongs_to :account
+ class RaisedOnSave < RuntimeError; end
+ attr_accessor :raise_on_save
+ before_save do
+ raise RaisedOnSave if raise_on_save
+ end
+
+ class RaisedOnDestroy < RuntimeError; end
+ attr_accessor :raise_on_destroy
+ before_destroy do
+ raise RaisedOnDestroy if raise_on_destroy
+ end
+
# Record destruction so we can test whether firm.clients.clear has
# is calling client.destroy, deleting from the database, or setting
# foreign keys to NULL.
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index a58c9bf572..967a3625aa 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -59,8 +59,9 @@ class LoosePerson < ActiveRecord::Base
has_one :best_friend, :class_name => 'LoosePerson', :foreign_key => :best_friend_id
belongs_to :best_friend_of, :class_name => 'LoosePerson', :foreign_key => :best_friend_of_id
-
has_many :best_friends, :class_name => 'LoosePerson', :foreign_key => :best_friend_id
+
+ accepts_nested_attributes_for :best_friend, :best_friend_of, :best_friends
end
class LooseDescendant < LoosePerson; end
@@ -70,11 +71,14 @@ class TightPerson < ActiveRecord::Base
attr_accessible :first_name, :gender
attr_accessible :first_name, :gender, :comments, :as => :admin
+ attr_accessible :best_friend_attributes, :best_friend_of_attributes, :best_friends_attributes
+ attr_accessible :best_friend_attributes, :best_friend_of_attributes, :best_friends_attributes, :as => :admin
has_one :best_friend, :class_name => 'TightPerson', :foreign_key => :best_friend_id
belongs_to :best_friend_of, :class_name => 'TightPerson', :foreign_key => :best_friend_of_id
-
has_many :best_friends, :class_name => 'TightPerson', :foreign_key => :best_friend_id
+
+ accepts_nested_attributes_for :best_friend, :best_friend_of, :best_friends
end
class TightDescendant < TightPerson; end \ No newline at end of file
diff --git a/activerecord/test/support/config.rb b/activerecord/test/support/config.rb
index 3b1598e368..6d123688a3 100644
--- a/activerecord/test/support/config.rb
+++ b/activerecord/test/support/config.rb
@@ -1,6 +1,7 @@
require 'yaml'
require 'erubis'
require 'fileutils'
+require 'pathname'
module ARTest
class << self
@@ -10,13 +11,16 @@ module ARTest
private
+ def config_file
+ Pathname.new(ENV['ARCONFIG'] || TEST_ROOT + '/config.yml')
+ end
+
def read_config
- unless File.exist?(TEST_ROOT + '/config.yml')
- FileUtils.cp TEST_ROOT + '/config.example.yml', TEST_ROOT + '/config.yml'
+ unless config_file.exist?
+ FileUtils.cp TEST_ROOT + '/config.example.yml', config_file
end
- raw = File.read(TEST_ROOT + '/config.yml')
- erb = Erubis::Eruby.new(raw)
+ erb = Erubis::Eruby.new(config_file.read)
expand_config(YAML.parse(erb.result(binding)).transform)
end
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index 6b7044aeae..23f25d6197 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,6 +1,12 @@
*Rails 3.2.0 (unreleased)*
-* Removed ActiveSupport::SecureRandom in favour of SecureRandom from the standard library [Jon Leighton]
+* Deprecated ActiveSupport::Memoizable in favor of Ruby memoization pattern [José Valim]
+
+* Added Time#all_day/week/quarter/year as a way of generating ranges (example: Event.where(created_at: Time.now.all_week)) [DHH]
+
+* Added instance_accessor: false as an option to Class#cattr_accessor and friends [DHH]
+
+* Removed ActiveSupport::SecureRandom in favor of SecureRandom from the standard library [Jon Leighton]
* ActiveSupport::OrderedHash now has different behavior for #each and
#each_pair when given a block accepting its parameters with a splat. [Andrew Radev]
diff --git a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
index a903735acf..268303aaf2 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
@@ -17,6 +17,7 @@ require 'active_support/core_ext/array/extract_options'
#
# To opt out of the instance writer method, pass :instance_writer => false.
# To opt out of the instance reader method, pass :instance_reader => false.
+# To opt out of both instance methods, pass :instance_accessor => false.
#
# class Person
# cattr_accessor :hair_colors, :instance_writer => false, :instance_reader => false
@@ -38,7 +39,7 @@ class Class
end
EOS
- unless options[:instance_reader] == false
+ unless options[:instance_reader] == false || options[:instance_accessor] == false
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{sym}
@@#{sym}
@@ -61,7 +62,7 @@ class Class
end
EOS
- unless options[:instance_writer] == false
+ unless options[:instance_writer] == false || options[:instance_accessor] == false
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{sym}=(obj)
@@#{sym} = obj
diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb
index 20e40fe40f..71f3879e49 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -76,10 +76,33 @@ end
module ActiveSupport #:nodoc:
class SafeBuffer < String
UNSAFE_STRING_METHODS = ["capitalize", "chomp", "chop", "delete", "downcase", "gsub", "lstrip", "next", "reverse", "rstrip", "slice", "squeeze", "strip", "sub", "succ", "swapcase", "tr", "tr_s", "upcase"].freeze
- alias safe_concat concat
+
+ alias_method :original_concat, :concat
+ private :original_concat
+
+ class SafeConcatError < StandardError
+ def initialize
+ super "Could not concatenate to the buffer because it is not html safe."
+ end
+ end
+
+ def safe_concat(value)
+ raise SafeConcatError if dirty?
+ original_concat(value)
+ end
+
+ def initialize(*)
+ @dirty = false
+ super
+ end
+
+ def initialize_copy(other)
+ super
+ @dirty = other.dirty?
+ end
def concat(value)
- if value.html_safe?
+ if dirty? || value.html_safe?
super(value)
else
super(ERB::Util.h(value))
@@ -92,15 +115,15 @@ module ActiveSupport #:nodoc:
end
def html_safe?
- true
+ !dirty?
end
- def html_safe
+ def to_s
self
end
- def to_s
- self
+ def to_param
+ to_str
end
def encode_with(coder)
@@ -109,7 +132,6 @@ module ActiveSupport #:nodoc:
def to_yaml(*args)
return super() if defined?(YAML::ENGINE) && !YAML::ENGINE.syck?
-
to_str.to_yaml(*args)
end
@@ -120,18 +142,21 @@ module ActiveSupport #:nodoc:
end
def #{unsafe_method}!(*args)
- raise TypeError, "Cannot modify SafeBuffer in place"
+ @dirty = true
+ super
end
EOT
end
+
+ protected
+
+ def dirty?
+ @dirty
+ end
end
end
class String
- def html_safe!
- raise "You can't call html_safe! on a String"
- end
-
def html_safe
ActiveSupport::SafeBuffer.new(self)
end
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 00fda2b370..c5fbbcf890 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -226,7 +226,7 @@ class Time
end
alias :at_end_of_quarter :end_of_quarter
- # Returns a new Time representing the start of the year (1st of january, 0:00)
+ # Returns a new Time representing the start of the year (1st of january, 0:00)
def beginning_of_year
change(:month => 1, :day => 1, :hour => 0)
end
@@ -248,6 +248,31 @@ class Time
advance(:days => 1)
end
+ # Returns a Range representing the whole day of the current time.
+ def all_day
+ beginning_of_day..end_of_day
+ end
+
+ # Returns a Range representing the whole week of the current time.
+ def all_week
+ beginning_of_week..end_of_week
+ end
+
+ # Returns a Range representing the whole month of the current time.
+ def all_month
+ beginning_of_month..end_of_month
+ end
+
+ # Returns a Range representing the whole quarter of the current time.
+ def all_quarter
+ beginning_of_quarter..end_of_quarter
+ end
+
+ # Returns a Range representing the whole year of the current time.
+ def all_year
+ beginning_of_year..end_of_year
+ end
+
def plus_with_duration(other) #:nodoc:
if ActiveSupport::Duration === other
other.since(self)
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 26c5c157cb..d1543c4c58 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -628,17 +628,6 @@ module ActiveSupport #:nodoc:
return []
end
- class LoadingModule #:nodoc:
- # Old style environment.rb referenced this method directly. Please note, it doesn't
- # actually *do* anything any more.
- def self.root(*args)
- if defined?(Rails) && Rails.logger
- Rails.logger.warn "Your environment.rb uses the old syntax, it may not continue to work in future releases."
- Rails.logger.warn "For upgrade instructions please see: http://manuals.rubyonrails.com/read/book/19"
- end
- end
- end
-
# Convert the provided const desc to a qualified constant name (as a string).
# A module, class, symbol, or string may be provided.
def to_constant_name(desc) #:nodoc:
diff --git a/activesupport/lib/active_support/memoizable.rb b/activesupport/lib/active_support/memoizable.rb
index 0a7bcd5bb8..4c67676ad5 100644
--- a/activesupport/lib/active_support/memoizable.rb
+++ b/activesupport/lib/active_support/memoizable.rb
@@ -1,8 +1,15 @@
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/module/aliasing'
+require 'active_support/deprecation'
module ActiveSupport
module Memoizable
+ def self.extended(base)
+ ActiveSupport::Deprecation.warn "ActiveSupport::Memoizable is deprecated and will be removed in future releases," \
+ "simply use Ruby memoization pattern instead.", caller
+ super
+ end
+
def self.memoized_ivar_for(symbol)
"@_memoized_#{symbol.to_s.sub(/\?\Z/, '_query').sub(/!\Z/, '_bang')}".to_sym
end
@@ -79,7 +86,11 @@ module ActiveSupport
else # else
def #{symbol}(*args) # def mime_type(*args)
#{memoized_ivar} ||= {} unless frozen? # @_memoized_mime_type ||= {} unless frozen?
- reload = args.pop if args.last == true || args.last == :reload # reload = args.pop if args.last == true || args.last == :reload
+ args_length = method(:#{original_method}).arity # args_length = method(:_unmemoized_mime_type).arity
+ if args.length == args_length + 1 && # if args.length == args_length + 1 &&
+ (args.last == true || args.last == :reload) # (args.last == true || args.last == :reload)
+ reload = args.pop # reload = args.pop
+ end # end
#
if defined?(#{memoized_ivar}) && #{memoized_ivar} # if defined?(@_memoized_mime_type) && @_memoized_mime_type
if !reload && #{memoized_ivar}.has_key?(args) # if !reload && @_memoized_mime_type.has_key?(args)
diff --git a/activesupport/test/core_ext/class/attribute_accessor_test.rb b/activesupport/test/core_ext/class/attribute_accessor_test.rb
index 456f4b7948..6b50f8db37 100644
--- a/activesupport/test/core_ext/class/attribute_accessor_test.rb
+++ b/activesupport/test/core_ext/class/attribute_accessor_test.rb
@@ -5,8 +5,9 @@ class ClassAttributeAccessorTest < Test::Unit::TestCase
def setup
@class = Class.new do
cattr_accessor :foo
- cattr_accessor :bar, :instance_writer => false
- cattr_reader :shaq, :instance_reader => false
+ cattr_accessor :bar, :instance_writer => false
+ cattr_reader :shaq, :instance_reader => false
+ cattr_accessor :camp, :instance_accessor => false
end
@object = @class.new
end
@@ -35,4 +36,10 @@ class ClassAttributeAccessorTest < Test::Unit::TestCase
assert_respond_to @class, :shaq
assert !@object.respond_to?(:shaq)
end
+
+ def test_should_not_create_instance_accessors
+ assert_respond_to @class, :camp
+ assert !@object.respond_to?(:camp)
+ assert !@object.respond_to?(:camp=)
+ end
end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 32675c884a..7a8cda160d 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -251,7 +251,7 @@ class StringInflectionsTest < Test::Unit::TestCase
# And changes the original string:
assert_equal original, expected
end
-
+
def test_string_inquiry
assert "production".inquiry.production?
assert !"production".inquiry.development?
@@ -451,6 +451,12 @@ class OutputSafetyTest < ActiveSupport::TestCase
assert !'ruby'.encoding_aware?
end
end
+
+ test "call to_param returns a normal string" do
+ string = @string.html_safe
+ assert string.html_safe?
+ assert !string.to_param.html_safe?
+ end
end
class StringExcludeTest < ActiveSupport::TestCase
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index 44e02109b1..c4c4381957 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -767,6 +767,26 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal false, Time === DateTime.civil(2000)
end
+ def test_all_day
+ assert_equal Time.local(2011,6,7,0,0,0)..Time.local(2011,6,7,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_day
+ end
+
+ def test_all_week
+ assert_equal Time.local(2011,6,6,0,0,0)..Time.local(2011,6,12,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_week
+ end
+
+ def test_all_month
+ assert_equal Time.local(2011,6,1,0,0,0)..Time.local(2011,6,30,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_month
+ end
+
+ def test_all_quarter
+ assert_equal Time.local(2011,4,1,0,0,0)..Time.local(2011,6,30,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_quarter
+ end
+
+ def test_all_year
+ assert_equal Time.local(2011,1,1,0,0,0)..Time.local(2011,12,31,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_year
+ end
+
protected
def with_env_tz(new_tz = 'US/Eastern')
old_tz, ENV['TZ'] = ENV['TZ'], new_tz
diff --git a/activesupport/test/flush_cache_on_private_memoization_test.rb b/activesupport/test/flush_cache_on_private_memoization_test.rb
index a7db96eb71..20768b777a 100644
--- a/activesupport/test/flush_cache_on_private_memoization_test.rb
+++ b/activesupport/test/flush_cache_on_private_memoization_test.rb
@@ -2,7 +2,9 @@ require 'abstract_unit'
require 'test/unit'
class FlashCacheOnPrivateMemoizationTest < Test::Unit::TestCase
- extend ActiveSupport::Memoizable
+ ActiveSupport::Deprecation.silence do
+ extend ActiveSupport::Memoizable
+ end
def test_public
assert_method_unmemoizable :pub
diff --git a/activesupport/test/memoizable_test.rb b/activesupport/test/memoizable_test.rb
index bceac1315b..e333b9a78c 100644
--- a/activesupport/test/memoizable_test.rb
+++ b/activesupport/test/memoizable_test.rb
@@ -2,7 +2,9 @@ require 'abstract_unit'
class MemoizableTest < ActiveSupport::TestCase
class Person
- extend ActiveSupport::Memoizable
+ ActiveSupport::Deprecation.silence do
+ extend ActiveSupport::Memoizable
+ end
attr_reader :name_calls, :age_calls, :is_developer_calls, :name_query_calls
@@ -65,7 +67,9 @@ class MemoizableTest < ActiveSupport::TestCase
end
module Rates
- extend ActiveSupport::Memoizable
+ ActiveSupport::Deprecation.silence do
+ extend ActiveSupport::Memoizable
+ end
attr_reader :sales_tax_calls
def sales_tax(price)
@@ -77,7 +81,9 @@ class MemoizableTest < ActiveSupport::TestCase
end
class Calculator
- extend ActiveSupport::Memoizable
+ ActiveSupport::Deprecation.silence do
+ extend ActiveSupport::Memoizable
+ end
include Rates
attr_reader :fib_calls
@@ -96,6 +102,15 @@ class MemoizableTest < ActiveSupport::TestCase
end
memoize :fib
+ def add_or_subtract(i, j, add)
+ if add
+ i + j
+ else
+ i - j
+ end
+ end
+ memoize :add_or_subtract
+
def counter
@count ||= 0
@count += 1
@@ -199,9 +214,16 @@ class MemoizableTest < ActiveSupport::TestCase
assert_equal 13, @calculator.fib_calls
end
+ def test_memoization_with_boolean_arg
+ assert_equal 4, @calculator.add_or_subtract(2, 2, true)
+ assert_equal 2, @calculator.add_or_subtract(4, 2, false)
+ end
+
def test_object_memoization
[Company.new, Company.new, Company.new].each do |company|
- company.extend ActiveSupport::Memoizable
+ ActiveSupport::Deprecation.silence do
+ company.extend ActiveSupport::Memoizable
+ end
company.memoize :name
assert_equal "37signals", company.name
@@ -235,11 +257,15 @@ class MemoizableTest < ActiveSupport::TestCase
def test_double_memoization
assert_raise(RuntimeError) { Person.memoize :name }
person = Person.new
- person.extend ActiveSupport::Memoizable
+ ActiveSupport::Deprecation.silence do
+ person.extend ActiveSupport::Memoizable
+ end
assert_raise(RuntimeError) { person.memoize :name }
company = Company.new
- company.extend ActiveSupport::Memoizable
+ ActiveSupport::Deprecation.silence do
+ company.extend ActiveSupport::Memoizable
+ end
company.memoize :name
assert_raise(RuntimeError) { company.memoize :name }
end
diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb
index 3a9854358c..a8b39d2c38 100644
--- a/activesupport/test/safe_buffer_test.rb
+++ b/activesupport/test/safe_buffer_test.rb
@@ -4,6 +4,7 @@ begin
rescue LoadError
end
+require 'active_support/core_ext/string/inflections'
require 'yaml'
class SafeBufferTest < ActiveSupport::TestCase
@@ -45,7 +46,7 @@ class SafeBufferTest < ActiveSupport::TestCase
assert_equal ActiveSupport::SafeBuffer, new_buffer.class
end
- def test_to_yaml
+ test "Should be converted to_yaml" do
str = 'hello!'
buf = ActiveSupport::SafeBuffer.new str
yaml = buf.to_yaml
@@ -54,22 +55,53 @@ class SafeBufferTest < ActiveSupport::TestCase
assert_equal 'hello!', YAML.load(yaml)
end
- def test_nested
+ test "Should work in nested to_yaml conversion" do
str = 'hello!'
data = { 'str' => ActiveSupport::SafeBuffer.new(str) }
yaml = YAML.dump data
assert_equal({'str' => str}, YAML.load(yaml))
end
+ test "Should work with underscore" do
+ str = "MyTest".html_safe.underscore
+ assert_equal "my_test", str
+ end
+
test "Should not return safe buffer from gsub" do
altered_buffer = @buffer.gsub('', 'asdf')
assert_equal 'asdf', altered_buffer
assert !altered_buffer.html_safe?
end
- test "Should not allow gsub! on safe buffers" do
- assert_raise TypeError do
- @buffer.gsub!('', 'asdf')
+ test "Should not return safe buffer from gsub!" do
+ @buffer.gsub!('', 'asdf')
+ assert_equal 'asdf', @buffer
+ assert !@buffer.html_safe?
+ end
+
+ test "Should escape dirty buffers on add" do
+ dirty = @buffer
+ clean = "hello".html_safe
+ @buffer.gsub!('', '<>')
+ assert_equal "hello&lt;&gt;", clean + @buffer
+ end
+
+ test "Should concat as a normal string when dirty" do
+ dirty = @buffer
+ clean = "hello".html_safe
+ @buffer.gsub!('', '<>')
+ assert_equal "<>hello", @buffer + clean
+ end
+
+ test "Should preserve dirty? status on copy" do
+ @buffer.gsub!('', '<>')
+ assert !@buffer.dup.html_safe?
+ end
+
+ test "Should raise an error when safe_concat is called on dirty buffers" do
+ @buffer.gsub!('', '<>')
+ assert_raise ActiveSupport::SafeBuffer::SafeConcatError do
+ @buffer.safe_concat "BUSTED"
end
end
end
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index 880849d2ec..f2b0cf15cd 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,5 +1,7 @@
*Rails 3.2.0 (unreleased)*
+* Attributes on scaffold and model generators default to string. This allows the following: "rails g scaffold Post title body:text author" [José Valim]
+
* Removed old plugin generator (`rails generate plugin`) in favor of `rails plugin new` command. [Guillermo Iguaran]
* Removed old 'config.paths.app.controller' API in favor of 'config.paths["app/controller"]' API. [Guillermo Iguaran]
diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile
index 21708e0fba..221d20fee6 100644
--- a/railties/guides/source/active_support_core_extensions.textile
+++ b/railties/guides/source/active_support_core_extensions.textile
@@ -1152,8 +1152,12 @@ h3. Extensions to +String+
h4. Output Safety
+h5. Motivation
+
Inserting data into HTML templates needs extra care. For example you can't just interpolate +@review.title+ verbatim into an HTML page. On one hand if the review title is "Flanagan & Matz rules!" the output won't be well-formed because an ampersand has to be escaped as "&amp;amp;". On the other hand, depending on the application that may be a big security hole because users can inject malicious HTML setting a hand-crafted review title. Check out the "section about cross-site scripting in the Security guide":security.html#cross-site-scripting-xss for further information about the risks.
+h5. Safe Strings
+
Active Support has the concept of <i>(html) safe</i> strings since Rails 3. A safe string is one that is marked as being insertable into HTML as is. It is trusted, no matter whether it has been escaped or not.
Strings are considered to be <i>unsafe</i> by default:
@@ -1179,8 +1183,6 @@ s # => "<script>...</script>"
It is your responsibility to ensure calling +html_safe+ on a particular string is fine.
-NOTE: For performance reasons safe strings are implemented in a way that cannot offer an in-place +html_safe!+ variant.
-
If you append onto a safe string, either in-place with +concat+/<tt><<</tt>, or with <tt>+</tt>, the result is a safe string. Unsafe arguments are escaped:
<ruby>
@@ -1221,6 +1223,22 @@ end
NOTE: Defined in +active_support/core_ext/string/output_safety.rb+.
+h5. Transformation
+
+As a rule of thumb, except perhaps for concatenation as explained above, any method that may change a string gives you an unsafe string. These are +donwcase+, +gsub+, +strip+, +chomp+, +underscore+, etc.
+
+In the case of in-place transformations like +gsub!+ the receiver itself becomes unsafe.
+
+INFO: The safety bit is lost always, no matter whether the transformation actually changed something.
+
+h5. Conversion and Coercion
+
+Calling +to_s+ on a safe string returns a safe string, but coercion with +to_str+ returns an unsafe string.
+
+h5. Copying
+
+Calling +dup+ or +clone+ on safe strings yields safe strings.
+
h4. +squish+
The method +squish+ strips leading and trailing whitespace, and substitutes runs of whitespace with a single space each:
diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile
index ca11f757ff..8e6010ff79 100644
--- a/railties/guides/source/configuring.textile
+++ b/railties/guides/source/configuring.textile
@@ -345,7 +345,7 @@ And can reference in the view with the following code:
<%= stylesheet_link_tag :special %>
</ruby>
-* +ActionView::Helpers::AssetTagHelper::AssetPaths.cache_asset_ids+ With the cache enabled, the asset tag helper methods will make fewer expensive file system calls (the default implementation checks the file system timestamp). However this prevents you from modifying any asset files while the server is running.
+* +config.action_view.cache_asset_ids+ With the cache enabled, the asset tag helper methods will make fewer expensive file system calls (the default implementation checks the file system timestamp). However this prevents you from modifying any asset files while the server is running.
h4. Configuring Action Mailer
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index a2b2af98a6..fe29668c72 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -78,10 +78,6 @@ module Rails
require environment if environment
end
- def eager_load! #:nodoc:
- railties.all(&:eager_load!)
- super
- end
def reload_routes!
routes_reloader.reload!
@@ -100,14 +96,12 @@ module Rails
def load_tasks(app=self)
initialize_tasks
- railties.all { |r| r.load_tasks(app) }
super
self
end
def load_console(app=self)
initialize_console
- railties.all { |r| r.load_console(app) }
super
self
end
diff --git a/railties/lib/rails/application/railties.rb b/railties/lib/rails/application/railties.rb
index 4fc5e92837..8f3a3e8bbb 100644
--- a/railties/lib/rails/application/railties.rb
+++ b/railties/lib/rails/application/railties.rb
@@ -4,7 +4,7 @@ module Rails
class Application < Engine
class Railties < Rails::Engine::Railties
def all(&block)
- @all ||= railties + engines + super
+ @all ||= railties + engines + plugins
@all.each(&block) if block
@all
end
diff --git a/railties/lib/rails/commands/plugin.rb b/railties/lib/rails/commands/plugin.rb
index 048af7cbef..4df849447d 100644
--- a/railties/lib/rails/commands/plugin.rb
+++ b/railties/lib/rails/commands/plugin.rb
@@ -313,11 +313,11 @@ module Commands
o.separator ""
o.separator "EXAMPLES"
o.separator " Install a plugin from a subversion URL:"
- o.separator " #{@script_name} plugin install http://dev.rubyonrails.com/svn/rails/plugins/continuous_builder\n"
+ o.separator " #{@script_name} plugin install http://example.com/my_svn_plugin\n"
o.separator " Install a plugin from a git URL:"
o.separator " #{@script_name} plugin install git://github.com/SomeGuy/my_awesome_plugin.git\n"
o.separator " Install a plugin and add a svn:externals entry to vendor/plugins"
- o.separator " #{@script_name} plugin install -x continuous_builder\n"
+ o.separator " #{@script_name} plugin install -x my_svn_plugin\n"
end
end
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index b358de89d0..52c89274e7 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -395,12 +395,20 @@ module Rails
delegate :middleware, :root, :paths, :to => :config
delegate :engine_name, :isolated?, :to => "self.class"
- def load_tasks(*)
+ def load_tasks(app=self)
+ railties.all { |r| r.load_tasks(app) }
super
paths["lib/tasks"].existent.sort.each { |ext| load(ext) }
end
-
+
+ def load_console(app=self)
+ railties.all { |r| r.load_console(app) }
+ super
+ end
+
def eager_load!
+ railties.all(&:eager_load!)
+
config.eager_load_paths.each do |load_path|
matcher = /\A#{Regexp.escape(load_path)}\/(.*)\.rb\Z/
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index f07e090c25..1196e2b7eb 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -190,7 +190,7 @@ module Rails
def bundle_command(command)
say_status :run, "bundle #{command}"
- # We are going to shell out rather than invoking Bundle::CLI.new(command)
+ # We are going to shell out rather than invoking Bundler::CLI.new(command)
# because `rails new` loads the Thor gem and on the other hand bundler uses
# its own vendored Thor, which could be a different version. Running both
# things in the same process is a recipe for a night with paracetamol.
diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb
index 9450894b05..f9f89c9f1d 100644
--- a/railties/lib/rails/generators/generated_attribute.rb
+++ b/railties/lib/rails/generators/generated_attribute.rb
@@ -7,7 +7,7 @@ module Rails
attr_accessor :name, :type
def initialize(name, type)
- raise Thor::Error, "Missing type for attribute '#{name}'.\nExample: '#{name}:string' where string is the type." if type.blank?
+ type = :string if type.blank?
@name, @type = name, type.to_sym
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index 60e26755fe..06ed890e05 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -14,9 +14,6 @@
# Compress JavaScripts and CSS
config.assets.compress = true
- # Specify the default JavaScript compressor
- config.assets.js_compressor = :uglifier
-
# Specifies the header that your server uses for sending files
# (comment out if your front-end server doesn't support this)
config.action_dispatch.x_sendfile_header = "X-Sendfile" # Use 'X-Accel-Redirect' for nginx
diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
index d50f536164..ea81748464 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
@@ -54,5 +54,5 @@
# This is a legacy wild controller route that's not recommended for RESTful applications.
# Note: This route will make all actions in every controller accessible via GET requests.
- # match ':controller(/:action(/:id(.:format)))'
+ # match ':controller(/:action(/:id))(.:format)'
end
diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
index 807350316c..7c0a2b9cf4 100644
--- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
@@ -11,15 +11,15 @@ module Rails
def app
if mountable?
directory "app"
- template "#{app_templates_dir}/app/views/layouts/application.html.erb.tt",
+ template "app/views/layouts/application.html.erb.tt",
"app/views/layouts/#{name}/application.html.erb"
- empty_directory_with_gitkeep "app/assets/images"
+ empty_directory_with_gitkeep "app/assets/images/#{name}"
elsif full?
empty_directory_with_gitkeep "app/models"
empty_directory_with_gitkeep "app/controllers"
empty_directory_with_gitkeep "app/views"
empty_directory_with_gitkeep "app/helpers"
- empty_directory_with_gitkeep "app/assets/images"
+ empty_directory_with_gitkeep "app/assets/images/#{name}"
end
end
@@ -108,9 +108,9 @@ task :default => :test
def stylesheets
if mountable?
copy_file "#{app_templates_dir}/app/assets/stylesheets/application.css",
- "app/assets/stylesheets/application.css"
+ "app/assets/stylesheets/#{name}/application.css"
elsif full?
- empty_directory_with_gitkeep "app/assets/stylesheets"
+ empty_directory_with_gitkeep "app/assets/stylesheets/#{name}"
end
end
@@ -119,9 +119,9 @@ task :default => :test
if mountable?
template "#{app_templates_dir}/app/assets/javascripts/application.js.tt",
- "app/assets/javascripts/application.js"
+ "app/assets/javascripts/#{name}/application.js"
elsif full?
- empty_directory_with_gitkeep "app/assets/javascripts"
+ empty_directory_with_gitkeep "app/assets/javascripts/#{name}"
end
end
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/application.html.erb.tt
new file mode 100644
index 0000000000..01550dec2f
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/application.html.erb.tt
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title><%= camelized %></title>
+ <%%= stylesheet_link_tag "<%= name %>/application" %>
+ <%%= javascript_include_tag "<%= name %>/application" %>
+ <%%= csrf_meta_tags %>
+</head>
+<body>
+
+<%%= yield %>
+
+</body>
+</html>
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index f96dd6b9eb..8c88b25617 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -174,11 +174,11 @@ module Rails
def eager_load!
end
- def load_console(app)
+ def load_console(app=self)
self.class.console.each { |block| block.call(app) }
end
- def load_tasks(app)
+ def load_tasks(app=self)
extend Rake::DSL if defined? Rake::DSL
self.class.rake_tasks.each { |block| block.call(app) }
@@ -190,7 +190,7 @@ module Rails
end
end
- def load_generators(app)
+ def load_generators(app=self)
self.class.generators.each { |block| block.call(app) }
end
diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb
index aa4db6e12a..8c2c079fb8 100644
--- a/railties/test/application/initializers/i18n_test.rb
+++ b/railties/test/application/initializers/i18n_test.rb
@@ -52,7 +52,7 @@ module ApplicationTests
end
test "locale files should be added to the load path" do
- app_file "config/another_locale.yml", ""
+ app_file "config/another_locale.yml", "en:\nfoo: ~"
add_to_config <<-RUBY
config.i18n.load_path << config.root.join("config/another_locale.yml").to_s
@@ -131,7 +131,7 @@ en:
# Fallbacks
test "not using config.i18n.fallbacks does not initialize I18n.fallbacks" do
- I18n.backend = Class.new { include I18n::Backend::Base }.new
+ I18n.backend = Class.new(I18n::Backend::Simple).new
load_app
assert_no_fallbacks
end
@@ -145,7 +145,7 @@ en:
test "config.i18n.fallbacks = true initializes I18n.fallbacks with default settings even when backend changes" do
I18n::Railtie.config.i18n.fallbacks = true
- I18n::Railtie.config.i18n.backend = Class.new { include I18n::Backend::Base }.new
+ I18n::Railtie.config.i18n.backend = Class.new(I18n::Backend::Simple).new
load_app
assert I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks)
assert_fallbacks :de => [:de, :en]
diff --git a/railties/test/generators/generated_attribute_test.rb b/railties/test/generators/generated_attribute_test.rb
index 0d2e624f44..c9f8ab0a7b 100644
--- a/railties/test/generators/generated_attribute_test.rb
+++ b/railties/test/generators/generated_attribute_test.rb
@@ -113,15 +113,8 @@ class GeneratedAttributeTest < Rails::Generators::TestCase
end
end
- def test_nil_type_raises_exception
- assert_raise Thor::Error do
- create_generated_attribute(nil, 'title')
- end
- end
-
- def test_missing_type_raises_exception
- assert_raise Thor::Error do
- create_generated_attribute('', 'title')
- end
+ def test_blank_type_defaults_to_string_raises_exception
+ assert_equal :string, create_generated_attribute(nil, 'title').type
+ assert_equal :string, create_generated_attribute("", 'title').type
end
end
diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb
index 8c5ba9926b..1b0cb425c6 100644
--- a/railties/test/generators/model_generator_test.rb
+++ b/railties/test/generators/model_generator_test.rb
@@ -12,9 +12,15 @@ class ModelGeneratorTest < Rails::Generators::TestCase
end
def test_model_with_missing_attribute_type
- content = capture(:stderr) { run_generator ["post", "title:string", "body"] }
- assert_match(/Missing type for attribute 'body'/, content)
- assert_match(/Example: 'body:string' where string is the type/, content)
+ run_generator ["post", "title", "body:text", "author"]
+
+ assert_migration "db/migrate/create_posts.rb" do |m|
+ assert_method :change, m do |up|
+ assert_match(/t\.string :title/, up)
+ assert_match(/t\.text :body/, up)
+ assert_match(/t\.string :author/, up)
+ end
+ end
end
def test_invokes_default_orm
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index 283d99dd9e..be72391e58 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -66,7 +66,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_no_file "test"
end
- def test_database_entry_is_assed_by_default_in_full_mode
+ def test_database_entry_is_generated_for_sqlite3_by_default_in_full_mode
run_generator([destination_root, "--full"])
assert_file "test/dummy/config/database.yml", /sqlite/
assert_file "Gemfile", /^gem\s+["']sqlite3["']$/
@@ -101,19 +101,19 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
def test_skipping_javascripts_without_mountable_option
run_generator
- assert_no_file "app/assets/javascripts/application.js"
+ assert_no_file "app/assets/javascripts/bukkits/application.js"
assert_no_file "vendor/assets/javascripts/jquery.js"
assert_no_file "vendor/assets/javascripts/jquery_ujs.js"
end
def test_javascripts_generation
run_generator [destination_root, "--mountable"]
- assert_file "app/assets/javascripts/application.js"
+ assert_file "app/assets/javascripts/bukkits/application.js"
end
def test_jquery_is_the_default_javascript_library
run_generator [destination_root, "--mountable"]
- assert_file "app/assets/javascripts/application.js" do |contents|
+ assert_file "app/assets/javascripts/bukkits/application.js" do |contents|
assert_match %r{^//= require jquery}, contents
assert_match %r{^//= require jquery_ujs}, contents
end
@@ -124,7 +124,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
def test_other_javascript_libraries
run_generator [destination_root, "--mountable", '-j', 'prototype']
- assert_file "app/assets/javascripts/application.js" do |contents|
+ assert_file "app/assets/javascripts/bukkits/application.js" do |contents|
assert_match %r{^//= require prototype}, contents
assert_match %r{^//= require prototype_ujs}, contents
end
@@ -135,7 +135,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
def test_skip_javascripts
run_generator [destination_root, "--skip-javascript", "--mountable"]
- assert_no_file "app/assets/javascripts/application.js"
+ assert_no_file "app/assets/javascripts/bukkits/application.js"
assert_no_file "vendor/assets/javascripts/jquery.js"
assert_no_file "vendor/assets/javascripts/jquery_ujs.js"
end
@@ -161,9 +161,9 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
def test_creating_engine_in_full_mode
run_generator [destination_root, "--full"]
- assert_file "app/assets/javascripts"
- assert_file "app/assets/stylesheets"
- assert_file "app/assets/images"
+ assert_file "app/assets/javascripts/bukkits"
+ assert_file "app/assets/stylesheets/bukkits"
+ assert_file "app/assets/images/bukkits"
assert_file "app/models"
assert_file "app/controllers"
assert_file "app/views"
@@ -180,15 +180,19 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
def test_create_mountable_application_with_mountable_option
run_generator [destination_root, "--mountable"]
- assert_file "app/assets/javascripts"
- assert_file "app/assets/stylesheets"
- assert_file "app/assets/images"
+ assert_file "app/assets/javascripts/bukkits"
+ assert_file "app/assets/stylesheets/bukkits"
+ assert_file "app/assets/images/bukkits"
assert_file "config/routes.rb", /Bukkits::Engine.routes.draw do/
assert_file "lib/bukkits/engine.rb", /isolate_namespace Bukkits/
assert_file "test/dummy/config/routes.rb", /mount Bukkits::Engine => "\/bukkits"/
assert_file "app/controllers/bukkits/application_controller.rb", /module Bukkits\n class ApplicationController < ActionController::Base/
assert_file "app/helpers/bukkits/application_helper.rb", /module Bukkits\n module ApplicationHelper/
- assert_file "app/views/layouts/bukkits/application.html.erb", /<title>Bukkits<\/title>/
+ assert_file "app/views/layouts/bukkits/application.html.erb" do |contents|
+ assert_match "<title>Bukkits</title>", contents
+ assert_match /stylesheet_link_tag\s+['"]bukkits\/application['"]/, contents
+ assert_match /javascript_include_tag\s+['"]bukkits\/application['"]/, contents
+ end
end
def test_creating_gemspec
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index 2135ffac81..c3c6015291 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -272,8 +272,14 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
end
def test_scaffold_generator_outputs_error_message_on_missing_attribute_type
- content = capture(:stderr) { run_generator ["post", "title:string", "body"]}
- assert_match(/Missing type for attribute 'body'/, content)
- assert_match(/Example: 'body:string' where string is the type/, content)
+ run_generator ["post", "title", "body:text", "author"]
+
+ assert_migration "db/migrate/create_posts.rb" do |m|
+ assert_method :change, m do |up|
+ assert_match(/t\.string :title/, up)
+ assert_match(/t\.text :body/, up)
+ assert_match(/t\.string :author/, up)
+ end
+ end
end
end