aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile1
-rw-r--r--actionpack/CHANGELOG.md11
-rw-r--r--actionpack/lib/action_controller/test_case.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb5
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb36
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb17
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb27
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb11
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/tag_helper.rb5
-rw-r--r--actionpack/lib/action_view/helpers/tags.rb1
-rw-r--r--actionpack/lib/action_view/helpers/tags/time_field.rb14
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb72
-rw-r--r--actionpack/lib/action_view/template/handlers.rb2
-rw-r--r--actionpack/lib/action_view/template/handlers/raw.rb11
-rw-r--r--actionpack/lib/action_view/template/resolver.rb4
-rw-r--r--actionpack/test/dispatch/middleware_stack_test.rb9
-rw-r--r--actionpack/test/fixtures/plain_text.raw1
-rw-r--r--actionpack/test/fixtures/plain_text_with_characters.raw1
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb1
-rw-r--r--actionpack/test/template/benchmark_helper_test.rb2
-rw-r--r--actionpack/test/template/form_helper_test.rb26
-rw-r--r--actionpack/test/template/form_options_helper_test.rb4
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb5
-rw-r--r--actionpack/test/template/number_helper_test.rb2
-rw-r--r--actionpack/test/template/render_test.rb8
-rw-r--r--actionpack/test/template/tag_helper_test.rb4
-rw-r--r--actionpack/test/template/template_test.rb7
-rw-r--r--actionpack/test/template/testing/fixture_resolver_test.rb4
-rw-r--r--actionpack/test/template/testing/null_resolver_test.rb4
-rw-r--r--actionpack/test/template/text_helper_test.rb98
-rw-r--r--activemodel/lib/active_model/secure_password.rb15
-rw-r--r--activemodel/lib/active_model/serialization.rb2
-rw-r--r--activemodel/test/cases/secure_password_test.rb30
-rw-r--r--activemodel/test/models/administrator.rb3
-rw-r--r--activemodel/test/models/user.rb3
-rw-r--r--activemodel/test/models/visitor.rb7
-rw-r--r--activerecord/CHANGELOG.md66
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb6
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb23
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb21
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb8
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb27
-rw-r--r--activerecord/lib/active_record/integration.rb2
-rw-r--r--activerecord/lib/active_record/persistence.rb1
-rw-r--r--activerecord/lib/active_record/relation/merger.rb3
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb34
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/migration.rb9
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb2
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb26
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb145
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb9
-rw-r--r--activerecord/test/cases/base_test.rb20
-rw-r--r--activerecord/test/cases/deprecated_dynamic_methods_test.rb607
-rw-r--r--activerecord/test/cases/finder_test.rb344
-rw-r--r--activerecord/test/cases/helper.rb1
-rw-r--r--activerecord/test/cases/mass_assignment_security_test.rb17
-rw-r--r--activerecord/test/cases/migration/change_table_test.rb221
-rw-r--r--activerecord/test/cases/migration_test.rb222
-rw-r--r--activerecord/test/cases/named_scope_test.rb39
-rw-r--r--activerecord/test/cases/readonly_test.rb1
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb15
-rw-r--r--activerecord/test/cases/relation_test.rb7
-rw-r--r--activerecord/test/cases/relations_test.rb62
-rw-r--r--activerecord/test/schema/schema.rb1
-rw-r--r--activesupport/lib/active_support/callbacks.rb19
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/time/conversions.rb1
-rw-r--r--activesupport/lib/active_support/log_subscriber/test_helper.rb2
-rw-r--r--activesupport/lib/active_support/testing/performance.rb1
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb15
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb4
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb7
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb74
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb8
-rw-r--r--guides/source/form_helpers.textile8
-rw-r--r--railties/CHANGELOG.md2
-rw-r--r--railties/lib/rails/application/route_inspector.rb4
-rw-r--r--railties/lib/rails/generators/app_base.rb11
-rw-r--r--railties/lib/rails/generators/test_case.rb1
-rw-r--r--railties/test/application/route_inspect_test.rb56
-rw-r--r--railties/test/generators/namespaced_generators_test.rb2
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb9
90 files changed, 1508 insertions, 1149 deletions
diff --git a/Gemfile b/Gemfile
index 7ae8344b1f..ac835936ca 100644
--- a/Gemfile
+++ b/Gemfile
@@ -11,6 +11,7 @@ end
gem 'rack-test', github: "brynary/rack-test"
gem 'bcrypt-ruby', '~> 3.0.0'
gem 'jquery-rails'
+gem 'minitest', '~> 3.0.0'
if ENV['JOURNEY']
gem 'journey', path: ENV['JOURNEY']
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 432e90199a..e625bbe7af 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,16 @@
## Rails 4.0.0 (unreleased) ##
+* Add `divider` option to `grouped_options_for_select` to generate a separator
+ `optgroup` automatically, and deprecate `prompt` as third argument, in favor
+ of using an options hash. *Nicholas Greenfield*
+
+* Add `time_field` and `time_field_tag` helpers which render an `input[type="time"]` tag. *Alex Soulim*
+
+* Removed old text_helper apis for highlight, excerpt and word_wrap *Jeremy Walker*
+
+* Templates without a handler extension now raises a deprecation warning but still
+ defaults to ERb. In future releases, it will simply return the template contents. *Steve Klabnik*
+
* Remove `:disable_with` in favor of `'data-disable-with'` option from `submit_tag`, `button_tag` and `button_to` helpers.
*Carlos Galdino + Rafael Mendonça França*
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 76d07891c9..028a8d3fba 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -93,13 +93,13 @@ module ActionController
rendered = @templates
msg = message || sprintf("expecting <%s> but rendering with <%s>",
options.inspect, rendered.keys)
- assert_block(msg) do
+ matches_template =
if options
rendered.any? { |t,num| t.match(options) }
else
@templates.blank?
end
- end
+ assert matches_template, msg
when Hash
if options.key?(:layout)
expected_layout = options[:layout]
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index 12bc438be3..bbf734f103 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -75,6 +75,11 @@ module ActionDispatch
middlewares[i]
end
+ def unshift(*args, &block)
+ middleware = self.class::Middleware.new(*args, &block)
+ middlewares.unshift(middleware)
+ end
+
def initialize_copy(other)
self.middlewares = other.middlewares.dup
end
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index 95c588c00a..b3823bb496 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -35,6 +35,25 @@ module ActionDispatch
def path(params, request)
block.call params, request
end
+
+ def inspect
+ "redirect(#{status})"
+ end
+ end
+
+ class PathRedirect < Redirect
+ def path(params, request)
+ (params.empty? || !block.match(/%\{\w*\}/)) ? block : (block % escape(params))
+ end
+
+ def inspect
+ "redirect(#{status}, #{block})"
+ end
+
+ private
+ def escape(params)
+ Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }]
+ end
end
class OptionRedirect < Redirect # :nodoc:
@@ -56,6 +75,10 @@ module ActionDispatch
ActionDispatch::Http::URL.url_for url_options
end
+ def inspect
+ "redirect(#{status}, #{options.map{ |k,v| "#{k}: #{v}" }.join(', ')})"
+ end
+
private
def escape_path(params)
Hash[params.map{ |k,v| [k, URI.parser.escape(v)] }]
@@ -102,24 +125,15 @@ module ActionDispatch
def redirect(*args, &block)
options = args.extract_options!
status = options.delete(:status) || 301
+ path = args.shift
return OptionRedirect.new(status, options) if options.any?
-
- path = args.shift
-
- block = lambda { |params, request|
- (params.empty? || !path.match(/%\{\w*\}/)) ? path : (path % escape(params))
- } if String === path
+ return PathRedirect.new(status, path) if String === path
block = path if path.respond_to? :call
raise ArgumentError, "redirection argument not supported" unless block
Redirect.new status, block
end
-
- private
- def escape(params)
- Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }]
- end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index a7a4ce21ff..02c1250c76 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -274,7 +274,7 @@ module ActionView
# The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and
# plugin authors are encouraged to do so.
def image_path(source)
- asset_paths.compute_public_path(source, 'images')
+ source.present? ? asset_paths.compute_public_path(source, 'images') : ""
end
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
@@ -377,7 +377,7 @@ module ActionView
src = options[:src] = path_to_image(source)
- unless src =~ /^(?:cid|data):/
+ unless src =~ /^(?:cid|data):/ || src.blank?
options[:alt] = options.fetch(:alt){ image_alt(src) }
end
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index cc1f133196..6510610034 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -990,6 +990,23 @@ module ActionView
Tags::DateField.new(object_name, method, self, options).render
end
+ # Returns a text_field of type "time".
+ #
+ # The default value is generated by trying to call +strftime+ with "%T.%L"
+ # on the objects's value. It is still possible to override that
+ # by passing the "value" option.
+ #
+ # === Options
+ # * Accepts same options as time_field_tag
+ #
+ # === Example
+ # time_field("task", "started_at")
+ # # => <input id="task_started_at" name="task[started_at]" type="time" />
+ #
+ def time_field(object_name, method, options = {})
+ Tags::TimeField.new(object_name, method, self, options).render
+ end
+
# Returns a text_field of type "url".
#
# url_field("user", "homepage")
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index 52eb1aa447..eef426703d 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -477,8 +477,8 @@ module ActionView
#
# Sample usage (Hash):
# grouped_options = {
- # 'North America' => [['United States','US'], 'Canada'],
- # 'Europe' => ['Denmark','Germany','France']
+ # 'North America' => [['United States','US'], 'Canada'],
+ # 'Europe' => ['Denmark','Germany','France']
# }
# grouped_options_for_select(grouped_options)
#
@@ -495,10 +495,10 @@ module ActionView
#
# Sample usage (divider):
# grouped_options = [
- # [['United States','US'], 'Canada'],
- # ['Denmark','Germany','France']
+ # [['United States','US'], 'Canada'],
+ # ['Denmark','Germany','France']
# ]
- # grouped_options_for_select(grouped_options, divider: '---------')
+ # grouped_options_for_select(grouped_options, nil, divider: '---------')
#
# Possible output:
# <optgroup label="---------">
@@ -513,15 +513,14 @@ module ActionView
#
# <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
# wrap the output in an appropriate <tt><select></tt> tag.
- def grouped_options_for_select(*args)
- grouped_options = args.shift
- options = args.extract_options!
- selected_key = args.shift
- if prompt = args.shift
- ActiveSupport::Deprecation.warn 'Passing the prompt to grouped_options_for_select as an argument is deprecated. Please pass it in an options hash.'
- else
- prompt = options[:prompt]
+ def grouped_options_for_select(grouped_options, selected_key = nil, options = {})
+ if options.is_a?(Hash)
+ prompt = options[:prompt]
divider = options[:divider]
+ else
+ prompt = options
+ options = {}
+ ActiveSupport::Deprecation.warn "Passing the prompt to grouped_options_for_select as an argument is deprecated. Please use an options hash like `{ prompt: #{prompt.inspect} }`."
end
body = "".html_safe
@@ -534,7 +533,7 @@ module ActionView
grouped_options.each do |container|
if divider
- label, container = divider, container
+ label = divider
else
label, container = container
end
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 9e5c66f4a9..e65b4e3e95 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -549,6 +549,17 @@ module ActionView
text_field_tag(name, value, options.stringify_keys.update("type" => "date"))
end
+ # Creates a text field of type "time".
+ #
+ # === Options
+ # * <tt>:min</tt> - The minimum acceptable value.
+ # * <tt>:max</tt> - The maximum acceptable value.
+ # * <tt>:step</tt> - The acceptable value granularity.
+ # * Otherwise accepts the same options as text_field_tag.
+ def time_field_tag(name, value = nil, options = {})
+ text_field_tag(name, value, options.stringify_keys.update("type" => "time"))
+ end
+
# Creates a text field of type "url".
#
# ==== Options
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 62455b97f9..dfc26acfad 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -254,7 +254,7 @@ module ActionView
parts = number.to_s.to_str.split('.')
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
- parts.join(options[:separator]).html_safe
+ safe_join(parts, options[:separator])
end
# Formats a +number+ with the specified level of
diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb
index d5cd60e8a1..498be596ad 100644
--- a/actionpack/lib/action_view/helpers/tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/tag_helper.rb
@@ -154,8 +154,9 @@ module ActionView
def data_tag_option(key, value, escape)
key = "data-#{key.to_s.dasherize}"
- value = value.to_json if !value.is_a?(String) && !value.is_a?(Symbol)
-
+ unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
+ value = value.to_json
+ end
tag_option(key, value, escape)
end
diff --git a/actionpack/lib/action_view/helpers/tags.rb b/actionpack/lib/action_view/helpers/tags.rb
index 3cf762877f..5cd77c8ec3 100644
--- a/actionpack/lib/action_view/helpers/tags.rb
+++ b/actionpack/lib/action_view/helpers/tags.rb
@@ -25,6 +25,7 @@ module ActionView
autoload :TelField
autoload :TextArea
autoload :TextField
+ autoload :TimeField
autoload :TimeSelect
autoload :TimeZoneSelect
autoload :UrlField
diff --git a/actionpack/lib/action_view/helpers/tags/time_field.rb b/actionpack/lib/action_view/helpers/tags/time_field.rb
new file mode 100644
index 0000000000..271dc00c54
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/time_field.rb
@@ -0,0 +1,14 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TimeField < TextField #:nodoc:
+ def render
+ options = @options.stringify_keys
+ options["value"] = @options.fetch("value") { value(object).try(:strftime, "%T.%L") }
+ @options = options
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 33db4b05bf..8cd7cf0052 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -81,8 +81,7 @@ module ActionView
# truncate("<p>Once upon a time in a world far far away</p>")
# # => "<p>Once upon a time in a wo..."
def truncate(text, options = {})
- options.reverse_merge!(:length => 30)
- text.truncate(options.delete(:length), options) if text
+ text.truncate(options.fetch(:length, 30), options) if text
end
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into
@@ -101,25 +100,15 @@ module ActionView
#
# highlight('You searched for: rails', 'rails', :highlighter => '<a href="search?q=\1">\1</a>')
# # => You searched for: <a href="search?q=rails">rails</a>
- #
- # You can still use <tt>highlight</tt> with the old API that accepts the
- # +highlighter+ as its optional third parameter:
- #
- # highlight('You searched for: rails', 'rails', '<a href="search?q=\1">\1</a>')
- # # => You searched for: <a href="search?q=rails">rails</a>
- def highlight(text, phrases, *args)
- options = args.extract_options!
- unless args.empty?
- options[:highlighter] = args[0]
- end
- options[:highlighter] ||= '<mark>\1</mark>'
-
- text = sanitize(text) unless options[:sanitize] == false
+ def highlight(text, phrases, options = {})
+ highlighter = options.fetch(:highlighter, '<mark>\1</mark>')
+
+ text = sanitize(text) if options.fetch(:sanitize, true)
if text.blank? || phrases.blank?
text
else
match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
- text.gsub(/(#{match})(?![^<]*?>)/i, options[:highlighter])
+ text.gsub(/(#{match})(?![^<]*?>)/i, highlighter)
end.html_safe
end
@@ -143,22 +132,10 @@ module ActionView
#
# excerpt('This is also an example', 'an', :radius => 8, :omission => '<chop> ')
# # => <chop> is also an example
- #
- # You can still use <tt>excerpt</tt> with the old API that accepts the
- # +radius+ as its optional third and the +ellipsis+ as its
- # optional forth parameter:
- # excerpt('This is an example', 'an', 5) # => ...s is an exam...
- # excerpt('This is also an example', 'an', 8, '<chop> ') # => <chop> is also an example
- def excerpt(text, phrase, *args)
+ def excerpt(text, phrase, options = {})
return unless text && phrase
-
- options = args.extract_options!
- unless args.empty?
- options[:radius] = args[0]
- options[:omission] = args[1]
- end
- radius = options[:radius] || 100
- omission = options[:omission] || "..."
+ radius = options.fetch(:radius, 100)
+ omission = options.fetch(:omission, "...")
phrase = Regexp.escape(phrase)
return unless found_pos = text =~ /(#{phrase})/i
@@ -174,7 +151,7 @@ module ActionView
# Attempts to pluralize the +singular+ word unless +count+ is 1. If
# +plural+ is supplied, it will use that when count is > 1, otherwise
- # it will use the Inflector to determine the plural form
+ # it will use the Inflector to determine the plural form.
#
# pluralize(1, 'person')
# # => 1 person
@@ -188,7 +165,13 @@ module ActionView
# pluralize(0, 'person')
# # => 0 people
def pluralize(count, singular, plural = nil)
- "#{count || 0} " + ((count == 1 || count =~ /^1(\.0+)?$/) ? singular : (plural || singular.pluralize))
+ word = if (count == 1 || count =~ /^1(\.0+)?$/)
+ singular
+ else
+ plural || singular.pluralize
+ end
+
+ "#{count || 0} #{word}"
end
# Wraps the +text+ into lines no longer than +line_width+ width. This method
@@ -206,18 +189,8 @@ module ActionView
#
# word_wrap('Once upon a time', :line_width => 1)
# # => Once\nupon\na\ntime
- #
- # You can still use <tt>word_wrap</tt> with the old API that accepts the
- # +line_width+ as its optional second parameter:
- #
- # word_wrap('Once upon a time', 8)
- # # => Once\nupon a\ntime
- def word_wrap(text, *args)
- options = args.extract_options!
- unless args.blank?
- options[:line_width] = args[0]
- end
- line_width = options[:line_width] || 80
+ def word_wrap(text, options = {})
+ line_width = options.fetch(:line_width, 80)
text.split("\n").collect do |line|
line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line
@@ -256,9 +229,10 @@ 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 = sanitize(text) unless options[:sanitize] == false
+ def simple_format(text, html_options = {}, options = {})
wrapper_tag = options.fetch(:wrapper_tag, :p)
+
+ text = sanitize(text) if options.fetch(:sanitize, true)
paragraphs = split_paragraphs(text)
if paragraphs.empty?
@@ -309,7 +283,7 @@ module ActionView
# <% end %>
def cycle(first_value, *values)
options = values.extract_options!
- name = options.fetch(:name, "default")
+ name = options.fetch(:name, 'default')
values.unshift(first_value)
diff --git a/actionpack/lib/action_view/template/handlers.rb b/actionpack/lib/action_view/template/handlers.rb
index 4e22bec6cc..41b14373a3 100644
--- a/actionpack/lib/action_view/template/handlers.rb
+++ b/actionpack/lib/action_view/template/handlers.rb
@@ -4,10 +4,12 @@ module ActionView #:nodoc:
module Handlers #:nodoc:
autoload :ERB, 'action_view/template/handlers/erb'
autoload :Builder, 'action_view/template/handlers/builder'
+ autoload :Raw, 'action_view/template/handlers/raw'
def self.extended(base)
base.register_default_template_handler :erb, ERB.new
base.register_template_handler :builder, Builder.new
+ base.register_template_handler :raw, Raw.new
end
@@template_handlers = {}
diff --git a/actionpack/lib/action_view/template/handlers/raw.rb b/actionpack/lib/action_view/template/handlers/raw.rb
new file mode 100644
index 0000000000..0c0d1fffcb
--- /dev/null
+++ b/actionpack/lib/action_view/template/handlers/raw.rb
@@ -0,0 +1,11 @@
+module ActionView
+ module Template::Handlers
+ class Raw
+ def call(template)
+ escaped = template.source.gsub(':', '\:')
+
+ '%q:' + escaped + ':;'
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index 08155af013..fa2038f78d 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -171,7 +171,9 @@ module ActionView
def extract_handler_and_format(path, default_formats)
pieces = File.basename(path).split(".")
pieces.shift
- handler = Template.handler_for_extension(pieces.pop)
+ extension = pieces.pop
+ ActiveSupport::Deprecation.warn "The file #{path} did not specify a template handler. The default is currently ERB, but will change to RAW in the future." unless extension
+ handler = Template.handler_for_extension(extension)
format = pieces.last && Mime[pieces.last]
[handler, format]
end
diff --git a/actionpack/test/dispatch/middleware_stack_test.rb b/actionpack/test/dispatch/middleware_stack_test.rb
index 4191ed1ff4..948a690979 100644
--- a/actionpack/test/dispatch/middleware_stack_test.rb
+++ b/actionpack/test/dispatch/middleware_stack_test.rb
@@ -45,7 +45,7 @@ class MiddlewareStackTest < ActiveSupport::TestCase
assert_equal BazMiddleware, @stack.last.klass
assert_equal([true, {:foo => "bar"}], @stack.last.args)
end
-
+
test "use should push middleware class with block arguments onto the stack" do
proc = Proc.new {}
assert_difference "@stack.size" do
@@ -54,7 +54,7 @@ class MiddlewareStackTest < ActiveSupport::TestCase
assert_equal BlockMiddleware, @stack.last.klass
assert_equal proc, @stack.last.block
end
-
+
test "insert inserts middleware at the integer index" do
@stack.insert(1, BazMiddleware)
assert_equal BazMiddleware, @stack[1].klass
@@ -87,6 +87,11 @@ class MiddlewareStackTest < ActiveSupport::TestCase
assert_equal FooMiddleware, @stack[0].klass
end
+ test "unshift adds a new middleware at the beginning of the stack" do
+ @stack.unshift :"MiddlewareStackTest::BazMiddleware"
+ assert_equal BazMiddleware, @stack.first.klass
+ end
+
test "raise an error on invalid index" do
assert_raise RuntimeError do
@stack.insert("HiyaMiddleware", BazMiddleware)
diff --git a/actionpack/test/fixtures/plain_text.raw b/actionpack/test/fixtures/plain_text.raw
new file mode 100644
index 0000000000..b13985337f
--- /dev/null
+++ b/actionpack/test/fixtures/plain_text.raw
@@ -0,0 +1 @@
+<%= hello_world %>
diff --git a/actionpack/test/fixtures/plain_text_with_characters.raw b/actionpack/test/fixtures/plain_text_with_characters.raw
new file mode 100644
index 0000000000..1e86e44fb4
--- /dev/null
+++ b/actionpack/test/fixtures/plain_text_with_characters.raw
@@ -0,0 +1 @@
+Here are some characters: !@#$%^&*()-="'}{`
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index bfcc9dc7fe..7cc567c72d 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -205,6 +205,7 @@ class AssetTagHelperTest < ActionView::TestCase
%(image_tag("//www.rubyonrails.com/images/rails.png")) => %(<img alt="Rails" src="//www.rubyonrails.com/images/rails.png" />),
%(image_tag("mouse.png", :alt => nil)) => %(<img src="/images/mouse.png" />),
%(image_tag("data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==", :alt => nil)) => %(<img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" />),
+ %(image_tag("")) => %(<img src="" />)
}
FaviconLinkToTag = {
diff --git a/actionpack/test/template/benchmark_helper_test.rb b/actionpack/test/template/benchmark_helper_test.rb
index 1bdda22959..8c198d2562 100644
--- a/actionpack/test/template/benchmark_helper_test.rb
+++ b/actionpack/test/template/benchmark_helper_test.rb
@@ -19,6 +19,6 @@ class BenchmarkHelperTest < ActionView::TestCase
log = StringIO.new
self.stubs(:logger).returns(Logger.new(log))
benchmark {}
- assert_match(log.rewind && log.read, /Benchmarking \(\d+.\d+ms\)/)
+ assert_match(/Benchmarking \(\d+.\d+ms\)/, log.rewind && log.read)
end
end
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index beb3ea752a..27cc3ad48a 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -646,6 +646,32 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal(expected, date_field("post", "written_on"))
end
+ def test_time_field
+ expected = %{<input id="post_written_on" name="post[written_on]" type="time" value="00:00:00.000" />}
+ assert_dom_equal(expected, time_field("post", "written_on"))
+ end
+
+ def test_time_field_with_datetime_value
+ expected = %{<input id="post_written_on" name="post[written_on]" type="time" value="01:02:03.000" />}
+ @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
+ assert_dom_equal(expected, time_field("post", "written_on"))
+ end
+
+ def test_time_field_with_timewithzone_value
+ previous_time_zone, Time.zone = Time.zone, 'UTC'
+ expected = %{<input id="post_written_on" name="post[written_on]" type="time" value="01:02:03.000" />}
+ @post.written_on = Time.zone.parse('2004-06-15 01:02:03')
+ assert_dom_equal(expected, time_field("post", "written_on"))
+ ensure
+ Time.zone = previous_time_zone
+ end
+
+ def test_time_field_with_nil_value
+ expected = %{<input id="post_written_on" name="post[written_on]" type="time" />}
+ @post.written_on = nil
+ assert_dom_equal(expected, time_field("post", "written_on"))
+ end
+
def test_url_field
expected = %{<input id="user_homepage" name="user[homepage]" type="url" />}
assert_dom_equal(expected, url_field("user", "homepage"))
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index 9b64bc9d81..2322fb0406 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -300,12 +300,12 @@ class FormOptionsHelperTest < ActionView::TestCase
assert_dom_equal(
"<optgroup label=\"----------\"><option value=\"US\">US</option>\n<option value=\"Canada\">Canada</option></optgroup><optgroup label=\"----------\"><option value=\"GB\">GB</option>\n<option value=\"Germany\">Germany</option></optgroup>",
- grouped_options_for_select([['US',"Canada"] , ["GB", "Germany"]], divider: "----------")
+ grouped_options_for_select([['US',"Canada"] , ["GB", "Germany"]], nil, divider: "----------")
)
end
def test_grouped_options_for_select_with_selected_and_prompt_deprecated
- assert_deprecated 'Passing the prompt to grouped_options_for_select as an argument is deprecated. Please pass it in an options hash.' do
+ assert_deprecated 'Passing the prompt to grouped_options_for_select as an argument is deprecated. Please use an options hash like `{ prompt: "Choose a product..." }`.' do
assert_dom_equal(
"<option value=\"\">Choose a product...</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option selected=\"selected\" value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>",
grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], "Cowboy Hat", "Choose a product...")
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index 7a645217b8..6574e13558 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -459,6 +459,11 @@ class FormTagHelperTest < ActionView::TestCase
assert_dom_equal(expected, date_field_tag("cell"))
end
+ def test_time_field_tag
+ expected = %{<input id="cell" name="cell" type="time" />}
+ assert_dom_equal(expected, time_field_tag("cell"))
+ end
+
def test_url_field_tag
expected = %{<input id="homepage" name="homepage" type="url" />}
assert_dom_equal(expected, url_field_tag("homepage"))
diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb
index 5c6f23d70b..14ca6d9879 100644
--- a/actionpack/test/template/number_helper_test.rb
+++ b/actionpack/test/template/number_helper_test.rb
@@ -78,6 +78,8 @@ class NumberHelperTest < ActionView::TestCase
assert_equal '12,345,678-05', number_with_delimiter(12345678.05, :separator => '-')
assert_equal '12.345.678,05', number_with_delimiter(12345678.05, :separator => ',', :delimiter => '.')
assert_equal '12.345.678,05', number_with_delimiter(12345678.05, :delimiter => '.', :separator => ',')
+ assert_equal '1&lt;script&gt;&lt;/script&gt;01', number_with_delimiter(1.01, :separator => "<script></script>")
+ assert_equal '1&lt;script&gt;&lt;/script&gt;000', number_with_delimiter(1000, :delimiter => "<script></script>")
end
def test_number_with_precision
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index e7f5f100bf..88ed8664c2 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -79,6 +79,14 @@ module RenderTestCases
assert_equal "<h1>No Comment</h1>\n", @view.render(:template => "comments/empty", :handlers => [:builder])
end
+ def test_render_raw_template_with_handlers
+ assert_equal "<%= hello_world %>\n", @view.render(:template => "plain_text")
+ end
+
+ def test_render_raw_template_with_quotes
+ assert_equal %q;Here are some characters: !@#$%^&*()-="'}{`; + "\n", @view.render(:template => "plain_text_with_characters")
+ end
+
def test_render_file_with_localization_on_context_level
old_locale, @view.locale = @view.locale, :da
assert_equal "Hey verden", @view.render(:file => "test/hello_world")
diff --git a/actionpack/test/template/tag_helper_test.rb b/actionpack/test/template/tag_helper_test.rb
index 7161d107b3..f0a7ce0bc9 100644
--- a/actionpack/test/template/tag_helper_test.rb
+++ b/actionpack/test/template/tag_helper_test.rb
@@ -118,8 +118,8 @@ class TagHelperTest < ActionView::TestCase
def test_data_attributes
['data', :data].each { |data|
- assert_dom_equal '<a data-a-number="1" data-array="[1,2,3]" data-hash="{&quot;key&quot;:&quot;value&quot;}" data-string="hello" data-symbol="foo" />',
- tag('a', { data => { :a_number => 1, :string => 'hello', :symbol => :foo, :array => [1, 2, 3], :hash => { :key => 'value'} } })
+ assert_dom_equal '<a data-a-float="3.14" data-a-big-decimal="-123.456" data-a-number="1" data-array="[1,2,3]" data-hash="{&quot;key&quot;:&quot;value&quot;}" data-string="hello" data-symbol="foo" />',
+ tag('a', { data => { :a_float => 3.14, :a_big_decimal => BigDecimal.new("-123.456"), :a_number => 1, :string => 'hello', :symbol => :foo, :array => [1, 2, 3], :hash => { :key => 'value'} } })
}
end
end
diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb
index 8c57ada587..322bea3fb0 100644
--- a/actionpack/test/template/template_test.rb
+++ b/actionpack/test/template/template_test.rb
@@ -48,7 +48,7 @@ class TestERBTemplate < ActiveSupport::TestCase
end
def new_template(body = "<%= hello %>", details = {})
- ActionView::Template.new(body, "hello template", ERBHandler, {:virtual_path => "hello"}.merge!(details))
+ ActionView::Template.new(body, "hello template", details.fetch(:handler) { ERBHandler }, {:virtual_path => "hello"}.merge!(details))
end
def render(locals = {})
@@ -64,6 +64,11 @@ class TestERBTemplate < ActiveSupport::TestCase
assert_equal "Hello", render
end
+ def test_raw_template
+ @template = new_template("<%= hello %>", :handler => ActionView::Template::Handlers::Raw.new)
+ assert_equal "<%= hello %>", render
+ end
+
def test_template_loses_its_source_after_rendering
@template = new_template
render
diff --git a/actionpack/test/template/testing/fixture_resolver_test.rb b/actionpack/test/template/testing/fixture_resolver_test.rb
index de83540468..9649f349cb 100644
--- a/actionpack/test/template/testing/fixture_resolver_test.rb
+++ b/actionpack/test/template/testing/fixture_resolver_test.rb
@@ -8,8 +8,8 @@ class FixtureResolverTest < ActiveSupport::TestCase
end
def test_should_return_template_for_declared_path
- resolver = ActionView::FixtureResolver.new("arbitrary/path" => "this text")
- templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :handlers => []})
+ resolver = ActionView::FixtureResolver.new("arbitrary/path.erb" => "this text")
+ templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :handlers => [:erb]})
assert_equal 1, templates.size, "expected one template"
assert_equal "this text", templates.first.source
assert_equal "arbitrary/path", templates.first.virtual_path
diff --git a/actionpack/test/template/testing/null_resolver_test.rb b/actionpack/test/template/testing/null_resolver_test.rb
index e142506e6a..535ad3ab14 100644
--- a/actionpack/test/template/testing/null_resolver_test.rb
+++ b/actionpack/test/template/testing/null_resolver_test.rb
@@ -3,10 +3,10 @@ require 'abstract_unit'
class NullResolverTest < ActiveSupport::TestCase
def test_should_return_template_for_any_path
resolver = ActionView::NullResolver.new()
- templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :handlers => []})
+ templates = resolver.find_all("path.erb", "arbitrary", false, {:locale => [], :formats => [:html], :handlers => []})
assert_equal 1, templates.size, "expected one template"
assert_equal "Template generated by Null Resolver", templates.first.source
- assert_equal "arbitrary/path", templates.first.virtual_path
+ assert_equal "arbitrary/path.erb", templates.first.virtual_path
assert_equal [:html], templates.first.formats
end
end
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
index e5cb273518..f58e474759 100644
--- a/actionpack/test/template/text_helper_test.rb
+++ b/actionpack/test/template/text_helper_test.rb
@@ -60,6 +60,20 @@ class TextHelperTest < ActionView::TestCase
simple_format(text)
assert_equal text_clone, text
end
+
+ def test_simple_format_does_not_modify_the_html_options_hash
+ options = { :class => "foobar"}
+ passed_options = options.dup
+ simple_format("some text", passed_options)
+ assert_equal options, passed_options
+ end
+
+ def test_simple_format_does_not_modify_the_options_hash
+ options = { :wrapper_tag => :div, :sanitize => false }
+ passed_options = options.dup
+ simple_format("some text", {}, passed_options)
+ assert_equal options, passed_options
+ end
def test_truncate_should_not_be_html_safe
assert !truncate("Hello World!", :length => 12).html_safe?
@@ -92,6 +106,13 @@ class TextHelperTest < ActionView::TestCase
assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding('UTF-8'),
truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8'), :length => 10)
end
+
+ def test_truncate_does_not_modify_the_options_hash
+ options = { :length => 10 }
+ passed_options = options.dup
+ truncate("some text", passed_options)
+ assert_equal options, passed_options
+ end
def test_highlight_should_be_html_safe
assert highlight("This is a beautiful morning", "beautiful").html_safe?
@@ -110,7 +131,7 @@ class TextHelperTest < ActionView::TestCase
assert_equal(
"This is a <b>beautiful</b> morning, but also a <b>beautiful</b> day",
- highlight("This is a beautiful morning, but also a beautiful day", "beautiful", '<b>\1</b>')
+ highlight("This is a beautiful morning, but also a beautiful day", "beautiful", :highlighter => '<b>\1</b>')
)
assert_equal(
@@ -153,14 +174,7 @@ class TextHelperTest < ActionView::TestCase
end
def test_highlight_with_multiple_phrases_in_one_pass
- assert_equal %(<em>wow</em> <em>em</em>), highlight('wow em', %w(wow em), '<em>\1</em>')
- end
-
- def test_highlight_with_options_hash
- assert_equal(
- "This is a <b>beautiful</b> morning, but also a <b>beautiful</b> day",
- highlight("This is a beautiful morning, but also a beautiful day", "beautiful", :highlighter => '<b>\1</b>')
- )
+ assert_equal %(<em>wow</em> <em>em</em>), highlight('wow em', %w(wow em), :highlighter => '<em>\1</em>')
end
def test_highlight_with_html
@@ -189,42 +203,48 @@ class TextHelperTest < ActionView::TestCase
highlight("<div>abc div</div>", "div", :highlighter => '<b>\1</b>')
)
end
+
+ def test_highlight_does_not_modify_the_options_hash
+ options = { :highlighter => '<b>\1</b>', :sanitize => false }
+ passed_options = options.dup
+ highlight("<div>abc div</div>", "div", passed_options)
+ assert_equal options, passed_options
+ end
def test_excerpt
- assert_equal("...is a beautiful morn...", excerpt("This is a beautiful morning", "beautiful", 5))
- assert_equal("This is a...", excerpt("This is a beautiful morning", "this", 5))
- assert_equal("...iful morning", excerpt("This is a beautiful morning", "morning", 5))
+ assert_equal("...is a beautiful morn...", excerpt("This is a beautiful morning", "beautiful", :radius => 5))
+ assert_equal("This is a...", excerpt("This is a beautiful morning", "this", :radius => 5))
+ assert_equal("...iful morning", excerpt("This is a beautiful morning", "morning", :radius => 5))
assert_nil excerpt("This is a beautiful morning", "day")
end
def test_excerpt_should_not_be_html_safe
- assert !excerpt('This is a beautiful! morning', 'beautiful', 5).html_safe?
+ assert !excerpt('This is a beautiful! morning', 'beautiful', :radius => 5).html_safe?
end
def test_excerpt_in_borderline_cases
- assert_equal("", excerpt("", "", 0))
- assert_equal("a", excerpt("a", "a", 0))
- assert_equal("...b...", excerpt("abc", "b", 0))
- assert_equal("abc", excerpt("abc", "b", 1))
- assert_equal("abc...", excerpt("abcd", "b", 1))
- assert_equal("...abc", excerpt("zabc", "b", 1))
- assert_equal("...abc...", excerpt("zabcd", "b", 1))
- assert_equal("zabcd", excerpt("zabcd", "b", 2))
+ assert_equal("", excerpt("", "", :radius => 0))
+ assert_equal("a", excerpt("a", "a", :radius => 0))
+ assert_equal("...b...", excerpt("abc", "b", :radius => 0))
+ assert_equal("abc", excerpt("abc", "b", :radius => 1))
+ assert_equal("abc...", excerpt("abcd", "b", :radius => 1))
+ assert_equal("...abc", excerpt("zabc", "b", :radius => 1))
+ assert_equal("...abc...", excerpt("zabcd", "b", :radius => 1))
+ assert_equal("zabcd", excerpt("zabcd", "b", :radius => 2))
# excerpt strips the resulting string before ap-/prepending excerpt_string.
# whether this behavior is meaningful when excerpt_string is not to be
# appended is questionable.
- assert_equal("zabcd", excerpt(" zabcd ", "b", 4))
- assert_equal("...abc...", excerpt("z abc d", "b", 1))
+ assert_equal("zabcd", excerpt(" zabcd ", "b", :radius => 4))
+ assert_equal("...abc...", excerpt("z abc d", "b", :radius => 1))
end
def test_excerpt_with_regex
- assert_equal('...is a beautiful! mor...', excerpt('This is a beautiful! morning', 'beautiful', 5))
- assert_equal('...is a beautiful? mor...', excerpt('This is a beautiful? morning', 'beautiful', 5))
+ assert_equal('...is a beautiful! mor...', excerpt('This is a beautiful! morning', 'beautiful', :radius => 5))
+ assert_equal('...is a beautiful? mor...', excerpt('This is a beautiful? morning', 'beautiful', :radius => 5))
end
- def test_excerpt_with_options_hash
- assert_equal("...is a beautiful morn...", excerpt("This is a beautiful morning", "beautiful", :radius => 5))
+ def test_excerpt_with_omission
assert_equal("[...]is a beautiful morn[...]", excerpt("This is a beautiful morning", "beautiful", :omission => "[...]",:radius => 5))
assert_equal(
"This is the ultimate supercalifragilisticexpialidoceous very looooooooooooooooooong looooooooooooong beautiful morning with amazing sunshine and awesome tempera[...]",
@@ -234,19 +254,29 @@ class TextHelperTest < ActionView::TestCase
end
def test_excerpt_with_utf8
- assert_equal("...\357\254\203ciency could not be...".force_encoding('UTF-8'), excerpt("That's why e\357\254\203ciency could not be helped".force_encoding('UTF-8'), 'could', 8))
+ assert_equal("...\357\254\203ciency could not be...".force_encoding('UTF-8'), excerpt("That's why e\357\254\203ciency could not be helped".force_encoding('UTF-8'), 'could', :radius => 8))
+ end
+
+ def test_excerpt_does_not_modify_the_options_hash
+ options = { :omission => "[...]",:radius => 5 }
+ passed_options = options.dup
+ excerpt("This is a beautiful morning", "beautiful", passed_options)
+ assert_equal options, passed_options
end
def test_word_wrap
- assert_equal("my very very\nvery long\nstring", word_wrap("my very very very long string", 15))
+ assert_equal("my very very\nvery long\nstring", word_wrap("my very very very long string", :line_width => 15))
end
def test_word_wrap_with_extra_newlines
- assert_equal("my very very\nvery long\nstring\n\nwith another\nline", word_wrap("my very very very long string\n\nwith another line", 15))
- end
-
- def test_word_wrap_with_options_hash
- assert_equal("my very very\nvery long\nstring", word_wrap("my very very very long string", :line_width => 15))
+ assert_equal("my very very\nvery long\nstring\n\nwith another\nline", word_wrap("my very very very long string\n\nwith another line", :line_width => 15))
+ end
+
+ def test_word_wrap_does_not_modify_the_options_hash
+ options = { :line_width => 15 }
+ passed_options = options.dup
+ word_wrap("some text", passed_options)
+ assert_equal options, passed_options
end
def test_pluralization
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index 8711b24124..3eab745c89 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -6,8 +6,9 @@ module ActiveModel
# Adds methods to set and authenticate against a BCrypt password.
# This mechanism requires you to have a password_digest attribute.
#
- # Validations for presence of password, confirmation of password (using
+ # Validations for presence of password on create, confirmation of password (using
# a "password_confirmation" attribute) are automatically added.
+ # If you wish to turn off validations, pass 'validations: false' as an argument.
# You can add more validations by hand if need be.
#
# You need to add bcrypt-ruby (~> 3.0.0) to Gemfile to use has_secure_password:
@@ -31,16 +32,20 @@ module ActiveModel
# user.authenticate("mUc3m00RsqyRe") # => user
# User.find_by_name("david").try(:authenticate, "notright") # => false
# User.find_by_name("david").try(:authenticate, "mUc3m00RsqyRe") # => user
- def has_secure_password
+ def has_secure_password(options = {})
# Load bcrypt-ruby only when has_secure_password is used.
# This is to avoid ActiveModel (and by extension the entire framework) being dependent on a binary library.
gem 'bcrypt-ruby', '~> 3.0.0'
require 'bcrypt'
attr_reader :password
-
- validates_confirmation_of :password
- validates_presence_of :password_digest
+
+ if options.fetch(:validations, true)
+ validates_confirmation_of :password
+ validates_presence_of :password, :on => :create
+ end
+
+ before_create { raise "Password digest missing on new record" if password_digest.blank? }
include InstanceMethodsOnActivation
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb
index 3751e4a741..6d8fd21814 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -74,7 +74,7 @@ module ActiveModel
def serializable_hash(options = nil)
options ||= {}
- attribute_names = attributes.keys.sort
+ attribute_names = attributes.keys
if only = options[:only]
attribute_names &= Array(only).map(&:to_s)
elsif except = options[:except]
diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb
index c451cc1aca..5f18909301 100644
--- a/activemodel/test/cases/secure_password_test.rb
+++ b/activemodel/test/cases/secure_password_test.rb
@@ -7,16 +7,19 @@ class SecurePasswordTest < ActiveModel::TestCase
setup do
@user = User.new
+ @visitor = Visitor.new
end
test "blank password" do
- @user.password = ''
- assert !@user.valid?, 'user should be invalid'
+ @user.password = @visitor.password = ''
+ assert !@user.valid?(:create), 'user should be invalid'
+ assert @visitor.valid?(:create), 'visitor should be valid'
end
test "nil password" do
- @user.password = nil
- assert !@user.valid?, 'user should be invalid'
+ @user.password = @visitor.password = nil
+ assert !@user.valid?(:create), 'user should be invalid'
+ assert @visitor.valid?(:create), 'visitor should be valid'
end
test "blank password doesn't override previous password" do
@@ -26,15 +29,16 @@ class SecurePasswordTest < ActiveModel::TestCase
end
test "password must be present" do
- assert !@user.valid?
+ assert !@user.valid?(:create)
assert_equal 1, @user.errors.size
end
- test "password must match confirmation" do
- @user.password = "thiswillberight"
- @user.password_confirmation = "wrong"
+ test "match confirmation" do
+ @user.password = @visitor.password = "thiswillberight"
+ @user.password_confirmation = @visitor.password_confirmation = "wrong"
assert !@user.valid?
+ assert @visitor.valid?
@user.password_confirmation = "thiswillberight"
@@ -59,4 +63,14 @@ class SecurePasswordTest < ActiveModel::TestCase
assert !active_authorizer.include?(:password_digest)
assert active_authorizer.include?(:name)
end
+
+ test "User should not be created with blank digest" do
+ assert_raise RuntimeError do
+ @user.run_callbacks :create
+ end
+ @user.password = "supersecretpassword"
+ assert_nothing_raised do
+ @user.run_callbacks :create
+ end
+ end
end
diff --git a/activemodel/test/models/administrator.rb b/activemodel/test/models/administrator.rb
index a48f8b064f..2d6d34b3e2 100644
--- a/activemodel/test/models/administrator.rb
+++ b/activemodel/test/models/administrator.rb
@@ -1,7 +1,10 @@
class Administrator
+ extend ActiveModel::Callbacks
include ActiveModel::Validations
include ActiveModel::SecurePassword
include ActiveModel::MassAssignmentSecurity
+
+ define_model_callbacks :create
attr_accessor :name, :password_digest
attr_accessible :name
diff --git a/activemodel/test/models/user.rb b/activemodel/test/models/user.rb
index e221bb8091..4b11df12bf 100644
--- a/activemodel/test/models/user.rb
+++ b/activemodel/test/models/user.rb
@@ -1,6 +1,9 @@
class User
+ extend ActiveModel::Callbacks
include ActiveModel::Validations
include ActiveModel::SecurePassword
+
+ define_model_callbacks :create
has_secure_password
diff --git a/activemodel/test/models/visitor.rb b/activemodel/test/models/visitor.rb
index 36c0a16688..d15f448516 100644
--- a/activemodel/test/models/visitor.rb
+++ b/activemodel/test/models/visitor.rb
@@ -1,9 +1,12 @@
class Visitor
+ extend ActiveModel::Callbacks
include ActiveModel::Validations
include ActiveModel::SecurePassword
include ActiveModel::MassAssignmentSecurity
+
+ define_model_callbacks :create
- has_secure_password
+ has_secure_password(validations: false)
- attr_accessor :password_digest
+ attr_accessor :password_digest, :password_confirmation
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index d19e0c9e9b..8f1f315e42 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,71 @@
## Rails 4.0.0 (unreleased) ##
+* Deprecated most of the 'dynamic finder' methods. All dynamic methods
+ except for `find_by_...` and `find_by_...!` are deprecated. Here's
+ how you can rewrite the code:
+
+ * `find_all_by_...` can be rewritten using `where(...)`
+ * `find_last_by_...` can be rewritten using `where(...).last`
+ * `scoped_by_...` can be rewritten using `where(...)`
+ * `find_or_initialize_by_...` can be rewritten using
+ `where(...).first_or_initialize`
+ * `find_or_create_by_...` can be rewritten using
+ `where(...).first_or_create`
+ * `find_or_create_by_...!` can be rewritten using
+ `where(...).first_or_create!`
+
+ The implementation of the deprecated dynamic finders has been moved
+ to the `active_record_deprecated_finders` gem. See below for details.
+
+ *Jon Leighton*
+
+* Deprecated the old-style hash based finder API. This means that
+ methods which previously accepted "finder options" no longer do. For
+ example this:
+
+ Post.find(:all, :conditions => { :comments_count => 10 }, :limit => 5)
+
+ Should be rewritten in the new style which has existed since Rails 3:
+
+ Post.where(comments_count: 10).limit(5)
+
+ Note that as an interim step, it is possible to rewrite the above as:
+
+ Post.scoped(:where => { :comments_count => 10 }, :limit => 5)
+
+ This could save you a lot of work if there is a lot of old-style
+ finder usage in your application.
+
+ Calling `Post.scoped(options)` is a shortcut for
+ `Post.scoped.merge(options)`. `Relation#merge` now accepts a hash of
+ options, but they must be identical to the names of the equivalent
+ finder method. These are mostly identical to the old-style finder
+ option names, except in the following cases:
+
+ * `:conditions` becomes `:where`
+ * `:include` becomes `:includes`
+ * `:extend` becomes `:extending`
+
+ The code to implement the deprecated features has been moved out to
+ the `active_record_deprecated_finders` gem. This gem is a dependency
+ of Active Record in Rails 4.0. It will no longer be a dependency
+ from Rails 4.1, but if your app relies on the deprecated features
+ then you can add it to your own Gemfile. It will be maintained by
+ the Rails core team until Rails 5.0 is released.
+
+ *Jon Leighton*
+
+* It's not possible anymore to destroy a model marked as read only.
+
+ *Johannes Barre*
+
+* Added ability to ActiveRecord::Relation#from to accept other ActiveRecord::Relation objects
+
+ Record.from(subquery)
+ Record.from(subquery, :a)
+
+ *Radoslav Stankov*
+
* Added custom coders support for ActiveRecord::Store. Now you can set
your custom coder like this:
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index 2059d8acdf..9a6896dd55 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -69,12 +69,12 @@ module ActiveRecord::Associations::Builder
def define_restrict_dependency_method
name = self.name
mixin.redefine_method(dependency_method_name) do
- # has_many or has_one associations
- if send(name).respond_to?(:exists?) ? send(name).exists? : !send(name).nil?
+ has_one_macro = association(name).reflection.macro == :has_one
+ if has_one_macro ? !send(name).nil? : send(name).exists?
if dependent_restrict_raises?
raise ActiveRecord::DeleteRestrictionError.new(name)
else
- key = association(name).reflection.macro == :has_one ? "one" : "many"
+ key = has_one_macro ? "one" : "many"
errors.add(:base, :"restrict_dependent_destroy.#{key}",
:record => self.class.human_attribute_name(name).downcase)
return false
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index 0b634ab944..30fc44b4c2 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -18,7 +18,7 @@ module ActiveRecord::Associations::Builder
model.send(:include, Module.new {
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def destroy_associations
- association(#{name.to_sym.inspect}).delete_all_on_destroy
+ association(#{name.to_sym.inspect}).delete_all
super
end
RUBY
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index 9ddfd433e4..d37d4e9d33 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -42,7 +42,7 @@ module ActiveRecord::Associations::Builder
def define_delete_all_dependency_method
name = self.name
mixin.redefine_method(dependency_method_name) do
- association(name).delete_all_on_destroy
+ association(name).delete_all
end
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index aa354d9009..4ec176e641 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -156,19 +156,12 @@ module ActiveRecord
#
# See delete for more info.
def delete_all
- delete(load_target).tap do
+ delete(:all).tap do
reset
loaded!
end
end
- # Called when the association is declared as :dependent => :delete_all. This is
- # an optimised version which avoids loading the records into memory. Not really
- # for public consumption.
- def delete_all_on_destroy
- scoped.delete_all
- end
-
# Destroy all the records from this association.
#
# See destroy for more info.
@@ -228,7 +221,17 @@ module ActiveRecord
# are actually removed from the database, that depends precisely on
# +delete_records+. They are in any case removed from the collection.
def delete(*records)
- delete_or_destroy(records, options[:dependent])
+ dependent = options[:dependent]
+
+ if records.first == :all
+ if loaded? || dependent == :destroy
+ delete_or_destroy(load_target, dependent)
+ else
+ delete_records(:all, dependent)
+ end
+ else
+ delete_or_destroy(records, dependent)
+ end
end
# Destroy +records+ and remove them from this association calling
@@ -487,7 +490,7 @@ module ActiveRecord
"new records could not be saved."
end
- new_target
+ target
end
def concat_records(records)
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index a4cea99372..58d041ec1d 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -32,10 +32,6 @@ module ActiveRecord
record
end
- # ActiveRecord::Relation#delete_all needs to support joins before we can use a
- # SQL-only implementation.
- alias delete_all_on_destroy delete_all
-
private
def count_records
@@ -44,13 +40,20 @@ module ActiveRecord
def delete_records(records, method)
if sql = options[:delete_sql]
+ records = load_target if records == :all
records.each { |record| owner.connection.delete(interpolate(sql, record)) }
else
- relation = join_table
- stmt = relation.where(relation[reflection.foreign_key].eq(owner.id).
- and(relation[reflection.association_foreign_key].in(records.map { |x| x.id }.compact))
- ).compile_delete
- owner.connection.delete stmt
+ relation = join_table
+ condition = relation[reflection.foreign_key].eq(owner.id)
+
+ unless records == :all
+ condition = condition.and(
+ relation[reflection.association_foreign_key]
+ .in(records.map { |x| x.id }.compact)
+ )
+ end
+
+ owner.connection.delete(relation.where(condition).compile_delete)
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 059e6c77bc..e631579087 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -89,8 +89,12 @@ module ActiveRecord
records.each { |r| r.destroy }
update_counter(-records.length) unless inverse_updates_counter_cache?
else
- keys = records.map { |r| r[reflection.association_primary_key] }
- scope = scoped.where(reflection.association_primary_key => keys)
+ if records == :all
+ scope = scoped
+ else
+ keys = records.map { |r| r[reflection.association_primary_key] }
+ scope = scoped.where(reflection.association_primary_key => keys)
+ end
if method == :delete_all
update_counter(-scope.delete_all)
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 53d49fef2e..2683aaf5da 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -54,10 +54,6 @@ module ActiveRecord
record
end
- # ActiveRecord::Relation#delete_all needs to support joins before we can use a
- # SQL-only implementation.
- alias delete_all_on_destroy delete_all
-
private
def through_association
@@ -126,7 +122,12 @@ module ActiveRecord
def delete_records(records, method)
ensure_not_nested
- scope = through_association.scoped.where(construct_join_attributes(*records))
+ # This is unoptimised; it will load all the target records
+ # even when we just want to delete everything.
+ records = load_target if records == :all
+
+ scope = through_association.scoped
+ scope.where! construct_join_attributes(*records)
case method
when :destroy
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 6714970103..14bc95abfe 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -1264,14 +1264,25 @@ module ActiveRecord
# Maps logical Rails types to PostgreSQL-specific data types.
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
- return super unless type.to_s == 'integer'
- return 'integer' unless limit
-
- case limit
- when 1, 2; 'smallint'
- when 3, 4; 'integer'
- when 5..8; 'bigint'
- else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
+ case type.to_s
+ when 'binary'
+ # PostgreSQL doesn't support limits on binary (bytea) columns.
+ # The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
+ case limit
+ when nil, 0..0x3fffffff; super(type)
+ else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
+ end
+ when 'integer'
+ return 'integer' unless limit
+
+ case limit
+ when 1, 2; 'smallint'
+ when 3, 4; 'integer'
+ when 5..8; 'bigint'
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
+ end
+ else
+ super
end
end
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 2c42f4cca5..23c272ef12 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -39,7 +39,7 @@ module ActiveRecord
when new_record?
"#{self.class.model_name.cache_key}/new"
when timestamp = self[:updated_at]
- timestamp = timestamp.utc.to_s(:number)
+ timestamp = timestamp.utc.to_s(:nsec)
"#{self.class.model_name.cache_key}/#{id}-#{timestamp}"
else
"#{self.class.model_name.cache_key}/#{id}"
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 4a987c2343..a1bc39a32d 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -123,6 +123,7 @@ module ActiveRecord
# Deletes the record in the database and freezes this instance to reflect
# that no changes should be made (since they can't be persisted).
def destroy
+ raise ReadOnlyRecord if readonly?
destroy_associations
destroy_row if persisted?
@destroyed = true
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index 3f880ce5e9..36f98c6480 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -43,7 +43,7 @@ module ActiveRecord
def normal_values
Relation::SINGLE_VALUE_METHODS +
Relation::MULTI_VALUE_METHODS -
- [:where, :order, :bind, :reverse_order, :lock, :create_with, :reordering]
+ [:where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from]
end
def merge
@@ -76,6 +76,7 @@ module ActiveRecord
end
def merge_single_values
+ relation.from_value = values[:from] unless relation.from_value
relation.lock_value = values[:lock] unless relation.lock_value
relation.reverse_order_value = values[:reverse_order]
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 29536b16c4..19fe8155d9 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -300,12 +300,25 @@ module ActiveRecord
self
end
- def from(value)
- spawn.from!(value)
+ # Specifies table from which the records will be fetched. For example:
+ #
+ # Topic.select('title').from('posts')
+ # #=> SELECT title FROM posts
+ #
+ # Can accept other relation objects. For example:
+ #
+ # Topic.select('title').from(Topics.approved)
+ # # => SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
+ #
+ # Topics.select('a.title').from(Topics.approved, :a)
+ # # => SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
+ #
+ def from(value, subquery_name = nil)
+ spawn.from!(value, subquery_name)
end
- def from!(value)
- self.from_value = value
+ def from!(value, subquery_name = nil)
+ self.from_value = [value, subquery_name]
self
end
@@ -415,7 +428,7 @@ module ActiveRecord
build_select(arel, select_values.uniq)
arel.distinct(uniq_value)
- arel.from(from_value) if from_value
+ arel.from(build_from) if from_value
arel.lock(lock_value) if lock_value
arel
@@ -464,6 +477,17 @@ module ActiveRecord
end
end
+ def build_from
+ opts, name = from_value
+ case opts
+ when Relation
+ name ||= 'subquery'
+ opts.arel.as(name.to_s)
+ else
+ opts
+ end
+ end
+
def build_joins(manager, joins)
buckets = joins.group_by do |join|
case join
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
index b9b5ec7956..b1a0f83b5f 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
@@ -12,10 +12,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration
def up
<% attributes.each do |attribute| -%>
<%- if migration_action -%>
- <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><%= attribute.inject_options %><% end %>
- <%- if attribute.has_index? && migration_action == 'add' -%>
- add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
- <%- end -%>
+ <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %>
<%- end -%>
<%- end -%>
end
@@ -23,8 +20,8 @@ class <%= migration_class_name %> < ActiveRecord::Migration
def down
<% attributes.reverse.each do |attribute| -%>
<%- if migration_action -%>
- <%= migration_action == 'add' ? 'remove' : 'add' %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'remove' %>, :<%= attribute.type %><%= attribute.inject_options %><% end %>
- <%- if attribute.has_index? && migration_action == 'remove' -%>
+ add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %>
+ <%- if attribute.has_index? -%>
add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
<%- end -%>
<%- end -%>
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index f786452f94..8a7f44d0a3 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -37,7 +37,7 @@ module ActiveRecord
def test_time_column
owner = Owner.create!(:eats_at => Time.utc(1995,1,1,6,0))
- assert_match /1995-01-01/, owner.reload.eats_at.to_s
+ assert_match(/1995-01-01/, owner.reload.eats_at.to_s)
end
def test_exec_insert
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index b351196fbd..2c7a240915 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -518,7 +518,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
authors(:david).destroy
end
- assert_equal [], AuthorAddress.find_all_by_id([author_address.id, author_address_extra.id])
+ assert_equal [], AuthorAddress.where(id: [author_address.id, author_address_extra.id])
assert_equal [author_address.id], AuthorAddress.destroyed_author_address_ids
end
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index a6daf54c01..ed1caa2ef5 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -380,6 +380,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 0, active_record.developers_by_sql(true).size
end
+ def test_deleting_all_with_sql
+ project = Project.find(1)
+ project.developers_by_sql.delete_all
+ assert_equal 0, project.developers_by_sql.size
+ end
+
def test_deleting_all
david = Developer.find(1)
david.projects.reload
@@ -561,31 +567,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal high_id_jamis, projects(:active_record).developers.find_by_name('Jamis')
end
- def test_dynamic_find_all_should_respect_association_order
- # Developers are ordered 'name DESC, id DESC'
- low_id_jamis = developers(:jamis)
- middle_id_jamis = developers(:poor_jamis)
- high_id_jamis = projects(:active_record).developers.create(:name => 'Jamis')
-
- assert_equal [high_id_jamis, middle_id_jamis, low_id_jamis], projects(:active_record).developers.scoped(:where => "name = 'Jamis'").all
- assert_equal [high_id_jamis, middle_id_jamis, low_id_jamis], projects(:active_record).developers.find_all_by_name('Jamis')
- end
-
def test_find_should_append_to_association_order
ordered_developers = projects(:active_record).developers.order('projects.id')
assert_equal ['developers.name desc, developers.id desc', 'projects.id'], ordered_developers.order_values
end
- def test_dynamic_find_all_should_respect_association_limit
- assert_equal 1, projects(:active_record).limited_developers.scoped(:where => "name = 'Jamis'").all.length
- assert_equal 1, projects(:active_record).limited_developers.find_all_by_name('Jamis').length
- end
-
- def test_dynamic_find_all_order_should_override_association_limit
- assert_equal 2, projects(:active_record).limited_developers.scoped(:where => "name = 'Jamis'", :limit => 9_000).all.length
- assert_equal 2, projects(:active_record).limited_developers.find_all_by_name('Jamis', :limit => 9_000).length
- end
-
def test_dynamic_find_all_should_respect_readonly_access
projects(:active_record).readonly_developers.each { |d| assert_raise(ActiveRecord::ReadOnlyRecord) { d.save! } if d.valid?}
projects(:active_record).readonly_developers.each { |d| d.readonly? }
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 2e24f8ebe1..8b384c2513 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -221,21 +221,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal person, person.readers.first.person
end
- def test_find_or_create_by_resets_cached_counters
- person = Person.create! :first_name => 'tenderlove'
- post = Post.first
-
- assert_equal [], person.readers
- assert_nil person.readers.find_by_post_id(post.id)
-
- person.readers.find_or_create_by_post_id(post.id)
-
- assert_equal 1, person.readers.count
- assert_equal 1, person.readers.length
- assert_equal post, person.readers.first.post
- assert_equal person, person.readers.first.person
- end
-
def force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.each {|f| }
end
@@ -288,39 +273,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal ['id DESC', 'companies.id'], ordered_clients.order_values
end
- def test_dynamic_find_last_without_specified_order
- assert_equal companies(:second_client), companies(:first_firm).unsorted_clients.find_last_by_type('Client')
- end
-
def test_dynamic_find_should_respect_association_order
assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.scoped(:where => "type = 'Client'").first
assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.find_by_type('Client')
end
- def test_dynamic_find_all_should_respect_association_order
- assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.scoped(:where => "type = 'Client'").all
- assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.find_all_by_type('Client')
- end
-
- def test_dynamic_find_all_should_respect_association_limit
- assert_equal 1, companies(:first_firm).limited_clients.scoped(:where => "type = 'Client'").all.length
- assert_equal 1, companies(:first_firm).limited_clients.find_all_by_type('Client').length
- end
-
- def test_dynamic_find_all_limit_should_override_association_limit
- assert_equal 2, companies(:first_firm).limited_clients.scoped(:where => "type = 'Client'", :limit => 9_000).all.length
- assert_equal 2, companies(:first_firm).limited_clients.find_all_by_type('Client', :limit => 9_000).length
- end
-
- def test_dynamic_find_or_create_from_two_attributes_using_an_association
- author = authors(:david)
- number_of_posts = Post.count
- another = author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body")
- assert_equal number_of_posts + 1, Post.count
- assert_equal another, author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body")
- assert another.persisted?
- end
-
def test_cant_save_has_many_readonly_association
authors(:david).readonly_comments.each { |c| assert_raise(ActiveRecord::ReadOnlyRecord) { c.save! } }
authors(:david).readonly_comments.each { |c| assert c.readonly? }
@@ -727,57 +684,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert !companies(:first_firm).clients_of_firm.loaded?
end
- def test_find_or_initialize
- the_client = companies(:first_firm).clients.find_or_initialize_by_name("Yet another client")
- assert_equal companies(:first_firm).id, the_client.firm_id
- assert_equal "Yet another client", the_client.name
- assert !the_client.persisted?
- end
-
- def test_find_or_create_updates_size
- number_of_clients = companies(:first_firm).clients.size
- the_client = companies(:first_firm).clients.find_or_create_by_name("Yet another client")
- assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size
- assert_equal the_client, companies(:first_firm).clients.find_or_create_by_name("Yet another client")
- assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size
- end
-
- def test_find_or_initialize_updates_collection_size
- number_of_clients = companies(:first_firm).clients_of_firm.size
- companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client")
- assert_equal number_of_clients + 1, companies(:first_firm).clients_of_firm.size
- end
-
- def test_find_or_initialize_returns_the_instantiated_object
- client = companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client")
- assert_equal client, companies(:first_firm).clients_of_firm[-1]
- end
-
- def test_find_or_initialize_only_instantiates_a_single_object
- number_of_clients = Client.count
- companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client").save!
- companies(:first_firm).save!
- assert_equal number_of_clients+1, Client.count
- end
-
- def test_find_or_create_with_hash
- post = authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody')
- assert_equal post, authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody')
- assert post.persisted?
- end
-
- def test_find_or_create_with_one_attribute_followed_by_hash
- post = authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody')
- assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody')
- assert post.persisted?
- end
-
- def test_find_or_create_should_work_with_block
- post = authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'}
- assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'}
- assert post.persisted?
- end
-
def test_deleting
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first)
@@ -956,11 +862,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
Client.create(:client_of => firm.id, :name => "BigShot Inc.")
Client.create(:client_of => firm.id, :name => "SmallTime Inc.")
# only one of two clients is included in the association due to the :conditions key
- assert_equal 2, Client.find_all_by_client_of(firm.id).size
+ assert_equal 2, Client.where(client_of: firm.id).size
assert_equal 1, firm.dependent_conditional_clients_of_firm.size
firm.destroy
# only the correctly associated client should have been deleted
- assert_equal 1, Client.find_all_by_client_of(firm.id).size
+ assert_equal 1, Client.where(client_of: firm.id).size
end
def test_dependent_association_respects_optional_sanitized_conditions_on_delete
@@ -968,11 +874,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
Client.create(:client_of => firm.id, :name => "BigShot Inc.")
Client.create(:client_of => firm.id, :name => "SmallTime Inc.")
# only one of two clients is included in the association due to the :conditions key
- assert_equal 2, Client.find_all_by_client_of(firm.id).size
+ assert_equal 2, Client.where(client_of: firm.id).size
assert_equal 1, firm.dependent_sanitized_conditional_clients_of_firm.size
firm.destroy
# only the correctly associated client should have been deleted
- assert_equal 1, Client.find_all_by_client_of(firm.id).size
+ assert_equal 1, Client.where(client_of: firm.id).size
end
def test_dependent_association_respects_optional_hash_conditions_on_delete
@@ -980,11 +886,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
Client.create(:client_of => firm.id, :name => "BigShot Inc.")
Client.create(:client_of => firm.id, :name => "SmallTime Inc.")
# only one of two clients is included in the association due to the :conditions key
- assert_equal 2, Client.find_all_by_client_of(firm.id).size
+ assert_equal 2, Client.where(client_of: firm.id).size
assert_equal 1, firm.dependent_sanitized_conditional_clients_of_firm.size
firm.destroy
# only the correctly associated client should have been deleted
- assert_equal 1, Client.find_all_by_client_of(firm.id).size
+ assert_equal 1, Client.where(client_of: firm.id).size
end
def test_delete_all_association_with_primary_key_deletes_correct_records
@@ -1326,25 +1232,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal Comment.find(10), authors(:david).comments_desc.find_by_type('SpecialComment')
end
- def test_dynamic_find_all_should_respect_association_order_for_through
- assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.scoped(:where => "comments.type = 'SpecialComment'").all
- assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.find_all_by_type('SpecialComment')
- end
-
- def test_dynamic_find_all_should_respect_association_limit_for_through
- assert_equal 1, authors(:david).limited_comments.scoped(:where => "comments.type = 'SpecialComment'").all.length
- assert_equal 1, authors(:david).limited_comments.find_all_by_type('SpecialComment').length
- end
-
- def test_dynamic_find_all_order_should_override_association_limit_for_through
- assert_equal 4, authors(:david).limited_comments.scoped(:where => "comments.type = 'SpecialComment'", :limit => 9_000).all.length
- assert_equal 4, authors(:david).limited_comments.find_all_by_type('SpecialComment', :limit => 9_000).length
- end
-
- def test_find_all_include_over_the_same_table_for_through
- assert_equal 2, people(:michael).posts.scoped(:includes => :people).all.length
- end
-
def test_has_many_through_respects_hash_conditions
assert_equal authors(:david).hello_posts, authors(:david).hello_posts_with_hash_conditions
assert_equal authors(:david).hello_post_comments, authors(:david).hello_post_comments_with_hash_conditions
@@ -1456,13 +1343,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 1, author.essays.size
end
- assert_equal author.essays, Essay.find_all_by_writer_id("David")
+ assert_equal author.essays, Essay.where(writer_id: "David")
end
def test_has_many_custom_primary_key
david = authors(:david)
- assert_equal david.essays, Essay.find_all_by_writer_id("David")
+ assert_equal david.essays, Essay.where(writer_id: "David")
end
def test_blank_custom_primary_key_on_new_record_should_not_run_queries
@@ -1685,14 +1572,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [bulb2], car.reload.bulbs
end
- def test_replace_returns_new_target
+ def test_replace_returns_target
car = Car.create(:name => 'honda')
bulb1 = car.bulbs.create
bulb2 = car.bulbs.create
bulb3 = Bulb.create
assert_equal [bulb1, bulb2], car.bulbs
- result = car.bulbs.replace([bulb1, bulb3])
+ result = car.bulbs.replace([bulb3, bulb1])
assert_equal [bulb1, bulb3], car.bulbs
assert_equal [bulb1, bulb3], result
end
@@ -1727,4 +1614,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [client], firm.clients_of_firm
assert_equal [client], firm.reload.clients_of_firm
end
+
+ test "delete_all, when not loaded, doesn't load the records" do
+ post = posts(:welcome)
+
+ assert post.taggings_with_delete_all.count > 0
+ assert !post.taggings_with_delete_all.loaded?
+
+ # 2 queries: one DELETE and another to update the counter cache
+ assert_queries(2) do
+ post.taggings_with_delete_all.delete_all
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 20b0eeb5ea..ecc676f300 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -54,10 +54,6 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert_equal 1, authors(:mary).unique_categorized_posts.all.size
end
- def test_has_many_uniq_through_dynamic_find
- assert_equal 1, authors(:mary).unique_categorized_posts.find_all_by_title("So I was thinking").size
- end
-
def test_polymorphic_has_many_going_through_join_model
assert_equal tags(:general), tag = posts(:welcome).tags.first
assert_no_queries do
@@ -296,11 +292,6 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert_nil authors(:david).categories.scoped(:where => "categories.name = 'Technology'").first
end
- def test_has_many_class_methods_called_by_method_missing
- assert_equal categories(:general), authors(:david).categories.find_all_by_name('General').first
- assert_nil authors(:david).categories.find_by_name('Technology')
- end
-
def test_has_many_array_methods_called_by_method_missing
assert authors(:david).categories.any? { |category| category.name == 'General' }
assert_nothing_raised { authors(:david).categories.sort }
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 66a16d8b5f..619fb881fa 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -168,24 +168,6 @@ class BasicsTest < ActiveRecord::TestCase
assert Topic.table_exists?
end
- def test_finder_block
- t = Topic.first
- found = nil
- Topic.find_by_id(t.id) { |f| found = f }
- assert_equal t, found
- end
-
- def test_finder_block_nothing_found
- bad_id = Topic.maximum(:id) + 1
- assert_nil Topic.find_by_id(bad_id) { |f| raise }
- end
-
- def test_find_returns_block_value
- t = Topic.first
- x = Topic.find_by_id(t.id) { |f| "hi mom!" }
- assert_equal "hi mom!", x
- end
-
def test_preserving_date_objects
if current_adapter?(:SybaseAdapter)
# Sybase ctlib does not (yet?) support the date type; use datetime instead.
@@ -1939,7 +1921,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_cache_key_format_for_existing_record_with_updated_at
dev = Developer.first
- assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:number)}", dev.cache_key
+ assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:nsec)}", dev.cache_key
end
def test_cache_key_format_for_existing_record_with_nil_updated_at
diff --git a/activerecord/test/cases/deprecated_dynamic_methods_test.rb b/activerecord/test/cases/deprecated_dynamic_methods_test.rb
new file mode 100644
index 0000000000..77a609f49a
--- /dev/null
+++ b/activerecord/test/cases/deprecated_dynamic_methods_test.rb
@@ -0,0 +1,607 @@
+# This file should be deleted when active_record_deprecated_finders is removed as
+# a dependency.
+#
+# It is kept for now as there is some fairly nuanced behaviour in the dynamic
+# finders so it is useful to keep this around to guard against regressions if
+# we need to change the code.
+
+require 'cases/helper'
+require 'models/topic'
+require 'models/reply'
+require 'models/customer'
+require 'models/post'
+require 'models/company'
+require 'models/author'
+require 'models/category'
+require 'models/comment'
+require 'models/person'
+require 'models/reader'
+
+class DeprecatedDynamicMethodsTest < ActiveRecord::TestCase
+ fixtures :topics, :customers, :companies, :accounts, :posts, :categories, :categories_posts, :authors, :people, :comments, :readers
+
+ def setup
+ @deprecation_behavior = ActiveSupport::Deprecation.behavior
+ ActiveSupport::Deprecation.behavior = :silence
+ end
+
+ def teardown
+ ActiveSupport::Deprecation.behavior = @deprecation_behavior
+ end
+
+ def test_find_all_by_one_attribute
+ topics = Topic.find_all_by_content("Have a nice day")
+ assert_equal 2, topics.size
+ assert topics.include?(topics(:first))
+
+ assert_equal [], Topic.find_all_by_title("The First Topic!!")
+ end
+
+ def test_find_all_by_one_attribute_which_is_a_symbol
+ topics = Topic.find_all_by_content("Have a nice day".to_sym)
+ assert_equal 2, topics.size
+ assert topics.include?(topics(:first))
+
+ assert_equal [], Topic.find_all_by_title("The First Topic!!")
+ end
+
+ def test_find_all_by_one_attribute_that_is_an_aggregate
+ balance = customers(:david).balance
+ assert_kind_of Money, balance
+ found_customers = Customer.find_all_by_balance(balance)
+ assert_equal 1, found_customers.size
+ assert_equal customers(:david), found_customers.first
+ end
+
+ def test_find_all_by_two_attributes_that_are_both_aggregates
+ balance = customers(:david).balance
+ address = customers(:david).address
+ assert_kind_of Money, balance
+ assert_kind_of Address, address
+ found_customers = Customer.find_all_by_balance_and_address(balance, address)
+ assert_equal 1, found_customers.size
+ assert_equal customers(:david), found_customers.first
+ end
+
+ def test_find_all_by_two_attributes_with_one_being_an_aggregate
+ balance = customers(:david).balance
+ assert_kind_of Money, balance
+ found_customers = Customer.find_all_by_balance_and_name(balance, customers(:david).name)
+ assert_equal 1, found_customers.size
+ assert_equal customers(:david), found_customers.first
+ end
+
+ def test_find_all_by_one_attribute_with_options
+ topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC")
+ assert_equal topics(:first), topics.last
+
+ topics = Topic.find_all_by_content("Have a nice day", :order => "id")
+ assert_equal topics(:first), topics.first
+ end
+
+ def test_find_all_by_array_attribute
+ assert_equal 2, Topic.find_all_by_title(["The First Topic", "The Second Topic of the day"]).size
+ end
+
+ def test_find_all_by_boolean_attribute
+ topics = Topic.find_all_by_approved(false)
+ assert_equal 1, topics.size
+ assert topics.include?(topics(:first))
+
+ topics = Topic.find_all_by_approved(true)
+ assert_equal 3, topics.size
+ assert topics.include?(topics(:second))
+ end
+
+ def test_find_all_by_nil_and_not_nil_attributes
+ topics = Topic.find_all_by_last_read_and_author_name nil, "Mary"
+ assert_equal 1, topics.size
+ assert_equal "Mary", topics[0].author_name
+ end
+
+ def test_find_or_create_from_one_attribute
+ number_of_companies = Company.count
+ sig38 = Company.find_or_create_by_name("38signals")
+ assert_equal number_of_companies + 1, Company.count
+ assert_equal sig38, Company.find_or_create_by_name("38signals")
+ assert sig38.persisted?
+ end
+
+ def test_find_or_create_from_two_attributes
+ number_of_topics = Topic.count
+ another = Topic.find_or_create_by_title_and_author_name("Another topic","John")
+ assert_equal number_of_topics + 1, Topic.count
+ assert_equal another, Topic.find_or_create_by_title_and_author_name("Another topic", "John")
+ assert another.persisted?
+ end
+
+ def test_find_or_create_from_one_attribute_bang
+ number_of_companies = Company.count
+ assert_raises(ActiveRecord::RecordInvalid) { Company.find_or_create_by_name!("") }
+ assert_equal number_of_companies, Company.count
+ sig38 = Company.find_or_create_by_name!("38signals")
+ assert_equal number_of_companies + 1, Company.count
+ assert_equal sig38, Company.find_or_create_by_name!("38signals")
+ assert sig38.persisted?
+ end
+
+ def test_find_or_create_from_two_attributes_bang
+ number_of_companies = Company.count
+ assert_raises(ActiveRecord::RecordInvalid) { Company.find_or_create_by_name_and_firm_id!("", 17) }
+ assert_equal number_of_companies, Company.count
+ sig38 = Company.find_or_create_by_name_and_firm_id!("38signals", 17)
+ assert_equal number_of_companies + 1, Company.count
+ assert_equal sig38, Company.find_or_create_by_name_and_firm_id!("38signals", 17)
+ assert sig38.persisted?
+ assert_equal "38signals", sig38.name
+ assert_equal 17, sig38.firm_id
+ end
+
+ def test_find_or_create_from_two_attributes_with_one_being_an_aggregate
+ number_of_customers = Customer.count
+ created_customer = Customer.find_or_create_by_balance_and_name(Money.new(123), "Elizabeth")
+ assert_equal number_of_customers + 1, Customer.count
+ assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123), "Elizabeth")
+ assert created_customer.persisted?
+ end
+
+ def test_find_or_create_from_one_attribute_and_hash
+ number_of_companies = Company.count
+ sig38 = Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
+ assert_equal number_of_companies + 1, Company.count
+ assert_equal sig38, Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
+ assert sig38.persisted?
+ assert_equal "38signals", sig38.name
+ assert_equal 17, sig38.firm_id
+ assert_equal 23, sig38.client_of
+ end
+
+ def test_find_or_create_from_two_attributes_and_hash
+ number_of_companies = Company.count
+ sig38 = Company.find_or_create_by_name_and_firm_id({:name => "38signals", :firm_id => 17, :client_of => 23})
+ assert_equal number_of_companies + 1, Company.count
+ assert_equal sig38, Company.find_or_create_by_name_and_firm_id({:name => "38signals", :firm_id => 17, :client_of => 23})
+ assert sig38.persisted?
+ assert_equal "38signals", sig38.name
+ assert_equal 17, sig38.firm_id
+ assert_equal 23, sig38.client_of
+ end
+
+ def test_find_or_create_from_one_aggregate_attribute
+ number_of_customers = Customer.count
+ created_customer = Customer.find_or_create_by_balance(Money.new(123))
+ assert_equal number_of_customers + 1, Customer.count
+ assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123))
+ assert created_customer.persisted?
+ end
+
+ def test_find_or_create_from_one_aggregate_attribute_and_hash
+ number_of_customers = Customer.count
+ balance = Money.new(123)
+ name = "Elizabeth"
+ created_customer = Customer.find_or_create_by_balance({:balance => balance, :name => name})
+ assert_equal number_of_customers + 1, Customer.count
+ assert_equal created_customer, Customer.find_or_create_by_balance({:balance => balance, :name => name})
+ assert created_customer.persisted?
+ assert_equal balance, created_customer.balance
+ assert_equal name, created_customer.name
+ end
+
+ def test_find_or_initialize_from_one_attribute
+ sig38 = Company.find_or_initialize_by_name("38signals")
+ assert_equal "38signals", sig38.name
+ assert !sig38.persisted?
+ end
+
+ def test_find_or_initialize_from_one_aggregate_attribute
+ new_customer = Customer.find_or_initialize_by_balance(Money.new(123))
+ assert_equal 123, new_customer.balance.amount
+ assert !new_customer.persisted?
+ end
+
+ def test_find_or_initialize_from_one_attribute_should_not_set_attribute_even_when_protected
+ c = Company.find_or_initialize_by_name({:name => "Fortune 1000", :rating => 1000})
+ assert_equal "Fortune 1000", c.name
+ assert_not_equal 1000, c.rating
+ assert c.valid?
+ assert !c.persisted?
+ end
+
+ def test_find_or_create_from_one_attribute_should_not_set_attribute_even_when_protected
+ c = Company.find_or_create_by_name({:name => "Fortune 1000", :rating => 1000})
+ assert_equal "Fortune 1000", c.name
+ assert_not_equal 1000, c.rating
+ assert c.valid?
+ assert c.persisted?
+ end
+
+ def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected
+ c = Company.find_or_initialize_by_name_and_rating("Fortune 1000", 1000)
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000, c.rating
+ assert c.valid?
+ assert !c.persisted?
+ end
+
+ def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected
+ c = Company.find_or_create_by_name_and_rating("Fortune 1000", 1000)
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000, c.rating
+ assert c.valid?
+ assert c.persisted?
+ end
+
+ def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash
+ c = Company.find_or_initialize_by_rating(1000, {:name => "Fortune 1000"})
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000, c.rating
+ assert c.valid?
+ assert !c.persisted?
+ end
+
+ def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash
+ c = Company.find_or_create_by_rating(1000, {:name => "Fortune 1000"})
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000, c.rating
+ assert c.valid?
+ assert c.persisted?
+ end
+
+ def test_find_or_initialize_should_set_protected_attributes_if_given_as_block
+ c = Company.find_or_initialize_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000.to_f, c.rating.to_f
+ assert c.valid?
+ assert !c.persisted?
+ end
+
+ def test_find_or_create_should_set_protected_attributes_if_given_as_block
+ c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000.to_f, c.rating.to_f
+ assert c.valid?
+ assert c.persisted?
+ end
+
+ def test_find_or_create_should_work_with_block_on_first_call
+ class << Company
+ undef_method(:find_or_create_by_name) if method_defined?(:find_or_create_by_name)
+ end
+ c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000.to_f, c.rating.to_f
+ assert c.valid?
+ assert c.persisted?
+ end
+
+ def test_find_or_initialize_from_two_attributes
+ another = Topic.find_or_initialize_by_title_and_author_name("Another topic","John")
+ assert_equal "Another topic", another.title
+ assert_equal "John", another.author_name
+ assert !another.persisted?
+ end
+
+ def test_find_or_initialize_from_two_attributes_but_passing_only_one
+ assert_raise(ArgumentError) { Topic.find_or_initialize_by_title_and_author_name("Another topic") }
+ end
+
+ def test_find_or_initialize_from_one_aggregate_attribute_and_one_not
+ new_customer = Customer.find_or_initialize_by_balance_and_name(Money.new(123), "Elizabeth")
+ assert_equal 123, new_customer.balance.amount
+ assert_equal "Elizabeth", new_customer.name
+ assert !new_customer.persisted?
+ end
+
+ def test_find_or_initialize_from_one_attribute_and_hash
+ sig38 = Company.find_or_initialize_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
+ assert_equal "38signals", sig38.name
+ assert_equal 17, sig38.firm_id
+ assert_equal 23, sig38.client_of
+ assert !sig38.persisted?
+ end
+
+ def test_find_or_initialize_from_one_aggregate_attribute_and_hash
+ balance = Money.new(123)
+ name = "Elizabeth"
+ new_customer = Customer.find_or_initialize_by_balance({:balance => balance, :name => name})
+ assert_equal balance, new_customer.balance
+ assert_equal name, new_customer.name
+ assert !new_customer.persisted?
+ end
+
+ def test_find_last_by_one_attribute
+ assert_equal Topic.last, Topic.find_last_by_title(Topic.last.title)
+ assert_nil Topic.find_last_by_title("A title with no matches")
+ end
+
+ def test_find_last_by_invalid_method_syntax
+ assert_raise(NoMethodError) { Topic.fail_to_find_last_by_title("The First Topic") }
+ assert_raise(NoMethodError) { Topic.find_last_by_title?("The First Topic") }
+ end
+
+ def test_find_last_by_one_attribute_with_several_options
+ assert_equal accounts(:signals37), Account.order('id DESC').where('id != ?', 3).find_last_by_credit_limit(50)
+ end
+
+ def test_find_last_by_one_missing_attribute
+ assert_raise(NoMethodError) { Topic.find_last_by_undertitle("The Last Topic!") }
+ end
+
+ def test_find_last_by_two_attributes
+ topic = Topic.last
+ assert_equal topic, Topic.find_last_by_title_and_author_name(topic.title, topic.author_name)
+ assert_nil Topic.find_last_by_title_and_author_name(topic.title, "Anonymous")
+ end
+
+ def test_find_last_with_limit_gives_same_result_when_loaded_and_unloaded
+ scope = Topic.limit(2)
+ unloaded_last = scope.last
+ loaded_last = scope.all.last
+ assert_equal loaded_last, unloaded_last
+ end
+
+ def test_find_last_with_limit_and_offset_gives_same_result_when_loaded_and_unloaded
+ scope = Topic.offset(2).limit(2)
+ unloaded_last = scope.last
+ loaded_last = scope.all.last
+ assert_equal loaded_last, unloaded_last
+ end
+
+ def test_find_last_with_offset_gives_same_result_when_loaded_and_unloaded
+ scope = Topic.offset(3)
+ unloaded_last = scope.last
+ loaded_last = scope.all.last
+ assert_equal loaded_last, unloaded_last
+ end
+
+ def test_find_all_by_nil_attribute
+ topics = Topic.find_all_by_last_read nil
+ assert_equal 3, topics.size
+ assert topics.collect(&:last_read).all?(&:nil?)
+ end
+
+ def test_forwarding_to_dynamic_finders
+ welcome = Post.find(1)
+ assert_equal 4, Category.find_all_by_type('SpecialCategory').size
+ assert_equal 0, welcome.categories.find_all_by_type('SpecialCategory').size
+ assert_equal 2, welcome.categories.find_all_by_type('Category').size
+ end
+
+ def test_dynamic_find_all_should_respect_association_order
+ assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.scoped(:where => "type = 'Client'").all
+ assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.find_all_by_type('Client')
+ end
+
+ def test_dynamic_find_all_should_respect_association_limit
+ assert_equal 1, companies(:first_firm).limited_clients.scoped(:where => "type = 'Client'").all.length
+ assert_equal 1, companies(:first_firm).limited_clients.find_all_by_type('Client').length
+ end
+
+ def test_dynamic_find_all_limit_should_override_association_limit
+ assert_equal 2, companies(:first_firm).limited_clients.scoped(:where => "type = 'Client'", :limit => 9_000).all.length
+ assert_equal 2, companies(:first_firm).limited_clients.find_all_by_type('Client', :limit => 9_000).length
+ end
+
+ def test_dynamic_find_last_without_specified_order
+ assert_equal companies(:second_client), companies(:first_firm).unsorted_clients.find_last_by_type('Client')
+ end
+
+ def test_dynamic_find_or_create_from_two_attributes_using_an_association
+ author = authors(:david)
+ number_of_posts = Post.count
+ another = author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body")
+ assert_equal number_of_posts + 1, Post.count
+ assert_equal another, author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body")
+ assert another.persisted?
+ end
+
+ def test_dynamic_find_all_should_respect_association_order_for_through
+ assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.scoped(:where => "comments.type = 'SpecialComment'").all
+ assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.find_all_by_type('SpecialComment')
+ end
+
+ def test_dynamic_find_all_should_respect_association_limit_for_through
+ assert_equal 1, authors(:david).limited_comments.scoped(:where => "comments.type = 'SpecialComment'").all.length
+ assert_equal 1, authors(:david).limited_comments.find_all_by_type('SpecialComment').length
+ end
+
+ def test_dynamic_find_all_order_should_override_association_limit_for_through
+ assert_equal 4, authors(:david).limited_comments.scoped(:where => "comments.type = 'SpecialComment'", :limit => 9_000).all.length
+ assert_equal 4, authors(:david).limited_comments.find_all_by_type('SpecialComment', :limit => 9_000).length
+ end
+
+ def test_find_all_include_over_the_same_table_for_through
+ assert_equal 2, people(:michael).posts.scoped(:includes => :people).all.length
+ end
+
+ def test_find_or_create_by_resets_cached_counters
+ person = Person.create! :first_name => 'tenderlove'
+ post = Post.first
+
+ assert_equal [], person.readers
+ assert_nil person.readers.find_by_post_id(post.id)
+
+ person.readers.find_or_create_by_post_id(post.id)
+
+ assert_equal 1, person.readers.count
+ assert_equal 1, person.readers.length
+ assert_equal post, person.readers.first.post
+ assert_equal person, person.readers.first.person
+ end
+
+ def test_find_or_initialize
+ the_client = companies(:first_firm).clients.find_or_initialize_by_name("Yet another client")
+ assert_equal companies(:first_firm).id, the_client.firm_id
+ assert_equal "Yet another client", the_client.name
+ assert !the_client.persisted?
+ end
+
+ def test_find_or_create_updates_size
+ number_of_clients = companies(:first_firm).clients.size
+ the_client = companies(:first_firm).clients.find_or_create_by_name("Yet another client")
+ assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size
+ assert_equal the_client, companies(:first_firm).clients.find_or_create_by_name("Yet another client")
+ assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size
+ end
+
+ def test_find_or_initialize_updates_collection_size
+ number_of_clients = companies(:first_firm).clients_of_firm.size
+ companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client")
+ assert_equal number_of_clients + 1, companies(:first_firm).clients_of_firm.size
+ end
+
+ def test_find_or_initialize_returns_the_instantiated_object
+ client = companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client")
+ assert_equal client, companies(:first_firm).clients_of_firm[-1]
+ end
+
+ def test_find_or_initialize_only_instantiates_a_single_object
+ number_of_clients = Client.count
+ companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client").save!
+ companies(:first_firm).save!
+ assert_equal number_of_clients+1, Client.count
+ end
+
+ def test_find_or_create_with_hash
+ post = authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody')
+ assert_equal post, authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody')
+ assert post.persisted?
+ end
+
+ def test_find_or_create_with_one_attribute_followed_by_hash
+ post = authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody')
+ assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody')
+ assert post.persisted?
+ end
+
+ def test_find_or_create_should_work_with_block
+ post = authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'}
+ assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'}
+ assert post.persisted?
+ end
+
+ def test_forwarding_to_dynamic_finders_2
+ welcome = Post.find(1)
+ assert_equal 4, Comment.find_all_by_type('Comment').size
+ assert_equal 2, welcome.comments.find_all_by_type('Comment').size
+ end
+
+ def test_dynamic_find_all_by_attributes
+ authors = Author.scoped
+
+ davids = authors.find_all_by_name('David')
+ assert_kind_of Array, davids
+ assert_equal [authors(:david)], davids
+ end
+
+ def test_dynamic_find_or_initialize_by_attributes
+ authors = Author.scoped
+
+ lifo = authors.find_or_initialize_by_name('Lifo')
+ assert_equal "Lifo", lifo.name
+ assert !lifo.persisted?
+
+ assert_equal authors(:david), authors.find_or_initialize_by_name(:name => 'David')
+ end
+
+ def test_dynamic_find_or_create_by_attributes
+ authors = Author.scoped
+
+ lifo = authors.find_or_create_by_name('Lifo')
+ assert_equal "Lifo", lifo.name
+ assert lifo.persisted?
+
+ assert_equal authors(:david), authors.find_or_create_by_name(:name => 'David')
+ end
+
+ def test_dynamic_find_or_create_by_attributes_bang
+ authors = Author.scoped
+
+ assert_raises(ActiveRecord::RecordInvalid) { authors.find_or_create_by_name!('') }
+
+ lifo = authors.find_or_create_by_name!('Lifo')
+ assert_equal "Lifo", lifo.name
+ assert lifo.persisted?
+
+ assert_equal authors(:david), authors.find_or_create_by_name!(:name => 'David')
+ end
+
+ def test_finder_block
+ t = Topic.first
+ found = nil
+ Topic.find_by_id(t.id) { |f| found = f }
+ assert_equal t, found
+ end
+
+ def test_finder_block_nothing_found
+ bad_id = Topic.maximum(:id) + 1
+ assert_nil Topic.find_by_id(bad_id) { |f| raise }
+ end
+
+ def test_find_returns_block_value
+ t = Topic.first
+ x = Topic.find_by_id(t.id) { |f| "hi mom!" }
+ assert_equal "hi mom!", x
+ end
+
+ def test_dynamic_finder_with_invalid_params
+ assert_raise(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" }
+ end
+
+ def test_find_by_one_attribute_with_order_option
+ assert_equal accounts(:signals37), Account.find_by_credit_limit(50, :order => 'id')
+ assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :order => 'id DESC')
+ end
+
+ def test_dynamic_find_by_attributes_should_yield_found_object
+ david = authors(:david)
+ yielded_value = nil
+ Author.find_by_name(david.name) do |author|
+ yielded_value = author
+ end
+ assert_equal david, yielded_value
+ end
+end
+
+class DynamicScopeTest < ActiveRecord::TestCase
+ fixtures :posts
+
+ def setup
+ @test_klass = Class.new(Post) do
+ def self.name; "Post"; end
+ end
+ @deprecation_behavior = ActiveSupport::Deprecation.behavior
+ ActiveSupport::Deprecation.behavior = :silence
+ end
+
+ def teardown
+ ActiveSupport::Deprecation.behavior = @deprecation_behavior
+ end
+
+ def test_dynamic_scope
+ assert_equal @test_klass.scoped_by_author_id(1).find(1), @test_klass.find(1)
+ assert_equal @test_klass.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, @test_klass.scoped(:where => { :author_id => 1, :title => "Welcome to the weblog"}).first
+ end
+
+ def test_dynamic_scope_should_create_methods_after_hitting_method_missing
+ assert_blank @test_klass.methods.grep(/scoped_by_type/)
+ @test_klass.scoped_by_type(nil)
+ assert_present @test_klass.methods.grep(/scoped_by_type/)
+ end
+
+ def test_dynamic_scope_with_less_number_of_arguments
+ assert_raise(ArgumentError){ @test_klass.scoped_by_author_id_and_title(1) }
+ end
+end
+
+class DynamicScopeMatchTest < ActiveRecord::TestCase
+ def test_scoped_by_no_match
+ assert_nil ActiveRecord::DynamicMatchers::Method.match(nil, "not_scoped_at_all")
+ end
+
+ def test_scoped_by
+ match = ActiveRecord::DynamicMatchers::Method.match(nil, "scoped_by_age_and_sex_and_location")
+ assert_not_nil match
+ assert_equal %w(age sex location), match.attribute_names
+ end
+end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index c960773284..f7ecab28ce 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -310,7 +310,7 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_on_association_proxy_conditions
- assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10, 12], Comment.find_all_by_post_id(authors(:david).posts).map(&:id).sort
+ assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10, 12], Comment.where(post_id: authors(:david).posts).map(&:id).sort
end
def test_find_on_hash_conditions_with_range
@@ -589,11 +589,6 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordNotFound) { Topic.find_by_title!("The First Topic!") }
end
- def test_find_by_one_attribute_with_order_option
- assert_equal accounts(:signals37), Account.find_by_credit_limit(50, :order => 'id')
- assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :order => 'id DESC')
- end
-
def test_find_by_one_attribute_with_conditions
assert_equal accounts(:rails_core_account), Account.where('firm_id = ?', 6).find_by_credit_limit(50)
end
@@ -663,356 +658,21 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ArgumentError) { Topic.find_by_title_and_author_name("The First Topic") }
end
- def test_find_last_by_one_attribute
- assert_equal Topic.last, Topic.find_last_by_title(Topic.last.title)
- assert_nil Topic.find_last_by_title("A title with no matches")
- end
-
- def test_find_last_by_invalid_method_syntax
- assert_raise(NoMethodError) { Topic.fail_to_find_last_by_title("The First Topic") }
- assert_raise(NoMethodError) { Topic.find_last_by_title?("The First Topic") }
- end
-
- def test_find_last_by_one_attribute_with_several_options
- assert_equal accounts(:signals37), Account.order('id DESC').where('id != ?', 3).find_last_by_credit_limit(50)
- end
-
- def test_find_last_by_one_missing_attribute
- assert_raise(NoMethodError) { Topic.find_last_by_undertitle("The Last Topic!") }
- end
-
- def test_find_last_by_two_attributes
- topic = Topic.last
- assert_equal topic, Topic.find_last_by_title_and_author_name(topic.title, topic.author_name)
- assert_nil Topic.find_last_by_title_and_author_name(topic.title, "Anonymous")
- end
-
- def test_find_last_with_limit_gives_same_result_when_loaded_and_unloaded
- scope = Topic.limit(2)
- unloaded_last = scope.last
- loaded_last = scope.all.last
- assert_equal loaded_last, unloaded_last
- end
-
- def test_find_last_with_limit_and_offset_gives_same_result_when_loaded_and_unloaded
- scope = Topic.offset(2).limit(2)
- unloaded_last = scope.last
- loaded_last = scope.all.last
- assert_equal loaded_last, unloaded_last
- end
-
- def test_find_last_with_offset_gives_same_result_when_loaded_and_unloaded
- scope = Topic.offset(3)
- unloaded_last = scope.last
- loaded_last = scope.all.last
- assert_equal loaded_last, unloaded_last
- end
-
- def test_find_all_by_one_attribute
- topics = Topic.find_all_by_content("Have a nice day")
- assert_equal 2, topics.size
- assert topics.include?(topics(:first))
-
- assert_equal [], Topic.find_all_by_title("The First Topic!!")
- end
-
- def test_find_all_by_one_attribute_which_is_a_symbol
- topics = Topic.find_all_by_content("Have a nice day".to_sym)
- assert_equal 2, topics.size
- assert topics.include?(topics(:first))
-
- assert_equal [], Topic.find_all_by_title("The First Topic!!")
- end
-
- def test_find_all_by_one_attribute_that_is_an_aggregate
- balance = customers(:david).balance
- assert_kind_of Money, balance
- found_customers = Customer.find_all_by_balance(balance)
- assert_equal 1, found_customers.size
- assert_equal customers(:david), found_customers.first
- end
-
- def test_find_all_by_two_attributes_that_are_both_aggregates
- balance = customers(:david).balance
- address = customers(:david).address
- assert_kind_of Money, balance
- assert_kind_of Address, address
- found_customers = Customer.find_all_by_balance_and_address(balance, address)
- assert_equal 1, found_customers.size
- assert_equal customers(:david), found_customers.first
- end
-
- def test_find_all_by_two_attributes_with_one_being_an_aggregate
- balance = customers(:david).balance
- assert_kind_of Money, balance
- found_customers = Customer.find_all_by_balance_and_name(balance, customers(:david).name)
- assert_equal 1, found_customers.size
- assert_equal customers(:david), found_customers.first
- end
-
- def test_find_all_by_one_attribute_with_options
- topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC")
- assert_equal topics(:first), topics.last
-
- topics = Topic.find_all_by_content("Have a nice day", :order => "id")
- assert_equal topics(:first), topics.first
- end
-
- def test_find_all_by_array_attribute
- assert_equal 2, Topic.find_all_by_title(["The First Topic", "The Second Topic of the day"]).size
- end
-
- def test_find_all_by_boolean_attribute
- topics = Topic.find_all_by_approved(false)
- assert_equal 1, topics.size
- assert topics.include?(topics(:first))
-
- topics = Topic.find_all_by_approved(true)
- assert_equal 3, topics.size
- assert topics.include?(topics(:second))
- end
-
def test_find_by_nil_attribute
topic = Topic.find_by_last_read nil
assert_not_nil topic
assert_nil topic.last_read
end
- def test_find_all_by_nil_attribute
- topics = Topic.find_all_by_last_read nil
- assert_equal 3, topics.size
- assert topics.collect(&:last_read).all?(&:nil?)
- end
-
def test_find_by_nil_and_not_nil_attributes
topic = Topic.find_by_last_read_and_author_name nil, "Mary"
assert_equal "Mary", topic.author_name
end
- def test_find_all_by_nil_and_not_nil_attributes
- topics = Topic.find_all_by_last_read_and_author_name nil, "Mary"
- assert_equal 1, topics.size
- assert_equal "Mary", topics[0].author_name
- end
-
- def test_find_or_create_from_one_attribute
- number_of_companies = Company.count
- sig38 = Company.find_or_create_by_name("38signals")
- assert_equal number_of_companies + 1, Company.count
- assert_equal sig38, Company.find_or_create_by_name("38signals")
- assert sig38.persisted?
- end
-
- def test_find_or_create_from_two_attributes
- number_of_topics = Topic.count
- another = Topic.find_or_create_by_title_and_author_name("Another topic","John")
- assert_equal number_of_topics + 1, Topic.count
- assert_equal another, Topic.find_or_create_by_title_and_author_name("Another topic", "John")
- assert another.persisted?
- end
-
- def test_find_or_create_from_one_attribute_bang
- number_of_companies = Company.count
- assert_raises(ActiveRecord::RecordInvalid) { Company.find_or_create_by_name!("") }
- assert_equal number_of_companies, Company.count
- sig38 = Company.find_or_create_by_name!("38signals")
- assert_equal number_of_companies + 1, Company.count
- assert_equal sig38, Company.find_or_create_by_name!("38signals")
- assert sig38.persisted?
- end
-
- def test_find_or_create_from_two_attributes_bang
- number_of_companies = Company.count
- assert_raises(ActiveRecord::RecordInvalid) { Company.find_or_create_by_name_and_firm_id!("", 17) }
- assert_equal number_of_companies, Company.count
- sig38 = Company.find_or_create_by_name_and_firm_id!("38signals", 17)
- assert_equal number_of_companies + 1, Company.count
- assert_equal sig38, Company.find_or_create_by_name_and_firm_id!("38signals", 17)
- assert sig38.persisted?
- assert_equal "38signals", sig38.name
- assert_equal 17, sig38.firm_id
- end
-
- def test_find_or_create_from_two_attributes_with_one_being_an_aggregate
- number_of_customers = Customer.count
- created_customer = Customer.find_or_create_by_balance_and_name(Money.new(123), "Elizabeth")
- assert_equal number_of_customers + 1, Customer.count
- assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123), "Elizabeth")
- assert created_customer.persisted?
- end
-
- def test_find_or_create_from_one_attribute_and_hash
- number_of_companies = Company.count
- sig38 = Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
- assert_equal number_of_companies + 1, Company.count
- assert_equal sig38, Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
- assert sig38.persisted?
- assert_equal "38signals", sig38.name
- assert_equal 17, sig38.firm_id
- assert_equal 23, sig38.client_of
- end
-
- def test_find_or_create_from_two_attributes_and_hash
- number_of_companies = Company.count
- sig38 = Company.find_or_create_by_name_and_firm_id({:name => "38signals", :firm_id => 17, :client_of => 23})
- assert_equal number_of_companies + 1, Company.count
- assert_equal sig38, Company.find_or_create_by_name_and_firm_id({:name => "38signals", :firm_id => 17, :client_of => 23})
- assert sig38.persisted?
- assert_equal "38signals", sig38.name
- assert_equal 17, sig38.firm_id
- assert_equal 23, sig38.client_of
- end
-
- def test_find_or_create_from_one_aggregate_attribute
- number_of_customers = Customer.count
- created_customer = Customer.find_or_create_by_balance(Money.new(123))
- assert_equal number_of_customers + 1, Customer.count
- assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123))
- assert created_customer.persisted?
- end
-
- def test_find_or_create_from_one_aggregate_attribute_and_hash
- number_of_customers = Customer.count
- balance = Money.new(123)
- name = "Elizabeth"
- created_customer = Customer.find_or_create_by_balance({:balance => balance, :name => name})
- assert_equal number_of_customers + 1, Customer.count
- assert_equal created_customer, Customer.find_or_create_by_balance({:balance => balance, :name => name})
- assert created_customer.persisted?
- assert_equal balance, created_customer.balance
- assert_equal name, created_customer.name
- end
-
- def test_find_or_initialize_from_one_attribute
- sig38 = Company.find_or_initialize_by_name("38signals")
- assert_equal "38signals", sig38.name
- assert !sig38.persisted?
- end
-
- def test_find_or_initialize_from_one_aggregate_attribute
- new_customer = Customer.find_or_initialize_by_balance(Money.new(123))
- assert_equal 123, new_customer.balance.amount
- assert !new_customer.persisted?
- end
-
- def test_find_or_initialize_from_one_attribute_should_not_set_attribute_even_when_protected
- c = Company.find_or_initialize_by_name({:name => "Fortune 1000", :rating => 1000})
- assert_equal "Fortune 1000", c.name
- assert_not_equal 1000, c.rating
- assert c.valid?
- assert !c.persisted?
- end
-
- def test_find_or_create_from_one_attribute_should_not_set_attribute_even_when_protected
- c = Company.find_or_create_by_name({:name => "Fortune 1000", :rating => 1000})
- assert_equal "Fortune 1000", c.name
- assert_not_equal 1000, c.rating
- assert c.valid?
- assert c.persisted?
- end
-
- def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected
- c = Company.find_or_initialize_by_name_and_rating("Fortune 1000", 1000)
- assert_equal "Fortune 1000", c.name
- assert_equal 1000, c.rating
- assert c.valid?
- assert !c.persisted?
- end
-
- def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected
- c = Company.find_or_create_by_name_and_rating("Fortune 1000", 1000)
- assert_equal "Fortune 1000", c.name
- assert_equal 1000, c.rating
- assert c.valid?
- assert c.persisted?
- end
-
- def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash
- c = Company.find_or_initialize_by_rating(1000, {:name => "Fortune 1000"})
- assert_equal "Fortune 1000", c.name
- assert_equal 1000, c.rating
- assert c.valid?
- assert !c.persisted?
- end
-
- def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash
- c = Company.find_or_create_by_rating(1000, {:name => "Fortune 1000"})
- assert_equal "Fortune 1000", c.name
- assert_equal 1000, c.rating
- assert c.valid?
- assert c.persisted?
- end
-
- def test_find_or_initialize_should_set_protected_attributes_if_given_as_block
- c = Company.find_or_initialize_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
- assert_equal "Fortune 1000", c.name
- assert_equal 1000.to_f, c.rating.to_f
- assert c.valid?
- assert !c.persisted?
- end
-
- def test_find_or_create_should_set_protected_attributes_if_given_as_block
- c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
- assert_equal "Fortune 1000", c.name
- assert_equal 1000.to_f, c.rating.to_f
- assert c.valid?
- assert c.persisted?
- end
-
- def test_find_or_create_should_work_with_block_on_first_call
- class << Company
- undef_method(:find_or_create_by_name) if method_defined?(:find_or_create_by_name)
- end
- c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
- assert_equal "Fortune 1000", c.name
- assert_equal 1000.to_f, c.rating.to_f
- assert c.valid?
- assert c.persisted?
- end
-
- def test_find_or_initialize_from_two_attributes
- another = Topic.find_or_initialize_by_title_and_author_name("Another topic","John")
- assert_equal "Another topic", another.title
- assert_equal "John", another.author_name
- assert !another.persisted?
- end
-
- def test_find_or_initialize_from_two_attributes_but_passing_only_one
- assert_raise(ArgumentError) { Topic.find_or_initialize_by_title_and_author_name("Another topic") }
- end
-
- def test_find_or_initialize_from_one_aggregate_attribute_and_one_not
- new_customer = Customer.find_or_initialize_by_balance_and_name(Money.new(123), "Elizabeth")
- assert_equal 123, new_customer.balance.amount
- assert_equal "Elizabeth", new_customer.name
- assert !new_customer.persisted?
- end
-
- def test_find_or_initialize_from_one_attribute_and_hash
- sig38 = Company.find_or_initialize_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
- assert_equal "38signals", sig38.name
- assert_equal 17, sig38.firm_id
- assert_equal 23, sig38.client_of
- assert !sig38.persisted?
- end
-
- def test_find_or_initialize_from_one_aggregate_attribute_and_hash
- balance = Money.new(123)
- name = "Elizabeth"
- new_customer = Customer.find_or_initialize_by_balance({:balance => balance, :name => name})
- assert_equal balance, new_customer.balance
- assert_equal name, new_customer.name
- assert !new_customer.persisted?
- end
-
def test_find_with_bad_sql
assert_raise(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" }
end
- def test_dynamic_finder_with_invalid_params
- assert_raise(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" }
- end
-
def test_find_all_with_join
developers_on_project_one = Developer.scoped(
:joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
@@ -1051,7 +711,7 @@ class FinderTest < ActiveRecord::TestCase
# http://dev.rubyonrails.org/ticket/6778
def test_find_ignores_previously_inserted_record
Post.create!(:title => 'test', :body => 'it out')
- assert_equal [], Post.find_all_by_id(nil)
+ assert_equal [], Post.where(id: nil)
end
def test_find_by_empty_ids
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 20279f814b..37fa13f771 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -2,6 +2,7 @@ require File.expand_path('../../../../load_paths', __FILE__)
require 'config'
+gem 'minitest'
require 'minitest/autorun'
require 'stringio'
require 'mocha'
diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb
index 8122857f52..2f98d3c646 100644
--- a/activerecord/test/cases/mass_assignment_security_test.rb
+++ b/activerecord/test/cases/mass_assignment_security_test.rb
@@ -247,6 +247,23 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
assert !Task.new.respond_to?("#{method}=")
end
end
+end
+
+
+# This class should be deleted when we removed active_record_deprecated_finders as a
+# dependency.
+class MassAssignmentSecurityDeprecatedFindersTest < ActiveRecord::TestCase
+ include MassAssignmentTestHelpers
+
+ def setup
+ super
+ @deprecation_behavior = ActiveSupport::Deprecation.behavior
+ ActiveSupport::Deprecation.behavior = :silence
+ end
+
+ def teardown
+ ActiveSupport::Deprecation.behavior = @deprecation_behavior
+ end
def test_find_or_initialize_by_with_attr_accessible_attributes
p = TightPerson.find_or_initialize_by_first_name('Josh', attributes_hash)
diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb
new file mode 100644
index 0000000000..063209389f
--- /dev/null
+++ b/activerecord/test/cases/migration/change_table_test.rb
@@ -0,0 +1,221 @@
+require "cases/migration/helper"
+
+module ActiveRecord
+ class Migration
+ class TableTest < ActiveRecord::TestCase
+ class MockConnection < MiniTest::Mock
+ def native_database_types
+ {
+ :string => 'varchar(255)',
+ :integer => 'integer',
+ }
+ end
+
+ def type_to_sql(type, limit, precision, scale)
+ native_database_types[type]
+ end
+ end
+
+ def setup
+ @connection = MockConnection.new
+ end
+
+ def teardown
+ assert @connection.verify
+ end
+
+ def with_change_table
+ yield ConnectionAdapters::Table.new(:delete_me, @connection)
+ end
+
+ def test_references_column_type_adds_id
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, 'customer_id', :integer, {}]
+ t.references :customer
+ end
+ end
+
+ def test_remove_references_column_type_removes_id
+ with_change_table do |t|
+ @connection.expect :remove_column, nil, [:delete_me, 'customer_id']
+ t.remove_references :customer
+ end
+ end
+
+ def test_add_belongs_to_works_like_add_references
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, 'customer_id', :integer, {}]
+ t.belongs_to :customer
+ end
+ end
+
+ def test_remove_belongs_to_works_like_remove_references
+ with_change_table do |t|
+ @connection.expect :remove_column, nil, [:delete_me, 'customer_id']
+ t.remove_belongs_to :customer
+ end
+ end
+
+ def test_references_column_type_with_polymorphic_adds_type
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, 'taggable_id', :integer, {}]
+ @connection.expect :add_column, nil, [:delete_me, 'taggable_type', :string, {}]
+ t.references :taggable, :polymorphic => true
+ end
+ end
+
+ def test_remove_references_column_type_with_polymorphic_removes_type
+ with_change_table do |t|
+ @connection.expect :remove_column, nil, [:delete_me, 'taggable_id']
+ @connection.expect :remove_column, nil, [:delete_me, 'taggable_type']
+ t.remove_references :taggable, :polymorphic => true
+ end
+ end
+
+ def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, 'taggable_id', :integer, {:null => false}]
+ @connection.expect :add_column, nil, [:delete_me, 'taggable_type', :string, {:null => false}]
+ t.references :taggable, :polymorphic => true, :null => false
+ end
+ end
+
+ def test_remove_references_column_type_with_polymorphic_and_options_null_is_false_removes_table_flag
+ with_change_table do |t|
+ @connection.expect :remove_column, nil, [:delete_me, 'taggable_id']
+ @connection.expect :remove_column, nil, [:delete_me, 'taggable_type']
+ t.remove_references :taggable, :polymorphic => true, :null => false
+ end
+ end
+
+ def test_timestamps_creates_updated_at_and_created_at
+ with_change_table do |t|
+ @connection.expect :add_timestamps, nil, [:delete_me]
+ t.timestamps
+ end
+ end
+
+ def test_remove_timestamps_creates_updated_at_and_created_at
+ with_change_table do |t|
+ @connection.expect :remove_timestamps, nil, [:delete_me]
+ t.remove_timestamps
+ end
+ end
+
+ def string_column
+ @connection.native_database_types[:string]
+ end
+
+ def integer_column
+ @connection.native_database_types[:integer]
+ end
+
+ def test_integer_creates_integer_column
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, :foo, integer_column, {}]
+ @connection.expect :add_column, nil, [:delete_me, :bar, integer_column, {}]
+ t.integer :foo, :bar
+ end
+ end
+
+ def test_string_creates_string_column
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, :foo, string_column, {}]
+ @connection.expect :add_column, nil, [:delete_me, :bar, string_column, {}]
+ t.string :foo, :bar
+ end
+ end
+
+ def test_column_creates_column
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, :bar, :integer, {}]
+ t.column :bar, :integer
+ end
+ end
+
+ def test_column_creates_column_with_options
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, :bar, :integer, {:null => false}]
+ t.column :bar, :integer, :null => false
+ end
+ end
+
+ def test_index_creates_index
+ with_change_table do |t|
+ @connection.expect :add_index, nil, [:delete_me, :bar, {}]
+ t.index :bar
+ end
+ end
+
+ def test_index_creates_index_with_options
+ with_change_table do |t|
+ @connection.expect :add_index, nil, [:delete_me, :bar, {:unique => true}]
+ t.index :bar, :unique => true
+ end
+ end
+
+ def test_index_exists
+ with_change_table do |t|
+ @connection.expect :index_exists?, nil, [:delete_me, :bar, {}]
+ t.index_exists?(:bar)
+ end
+ end
+
+ def test_index_exists_with_options
+ with_change_table do |t|
+ @connection.expect :index_exists?, nil, [:delete_me, :bar, {:unique => true}]
+ t.index_exists?(:bar, :unique => true)
+ end
+ end
+
+ def test_change_changes_column
+ with_change_table do |t|
+ @connection.expect :change_column, nil, [:delete_me, :bar, :string, {}]
+ t.change :bar, :string
+ end
+ end
+
+ def test_change_changes_column_with_options
+ with_change_table do |t|
+ @connection.expect :change_column, nil, [:delete_me, :bar, :string, {:null => true}]
+ t.change :bar, :string, :null => true
+ end
+ end
+
+ def test_change_default_changes_column
+ with_change_table do |t|
+ @connection.expect :change_column_default, nil, [:delete_me, :bar, :string]
+ t.change_default :bar, :string
+ end
+ end
+
+ def test_remove_drops_single_column
+ with_change_table do |t|
+ @connection.expect :remove_column, nil, [:delete_me, :bar]
+ t.remove :bar
+ end
+ end
+
+ def test_remove_drops_multiple_columns
+ with_change_table do |t|
+ @connection.expect :remove_column, nil, [:delete_me, :bar, :baz]
+ t.remove :bar, :baz
+ end
+ end
+
+ def test_remove_index_removes_index_with_options
+ with_change_table do |t|
+ @connection.expect :remove_index, nil, [:delete_me, {:unique => true}]
+ t.remove_index :unique => true
+ end
+ end
+
+ def test_rename_renames_column
+ with_change_table do |t|
+ @connection.expect :rename_column, nil, [:delete_me, :bar, :baz]
+ t.rename :bar, :baz
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index cad93936c9..5d1bad0d54 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -421,228 +421,6 @@ class ReservedWordsMigrationTest < ActiveRecord::TestCase
end
end
-
-class ChangeTableMigrationsTest < ActiveRecord::TestCase
- def setup
- @connection = Person.connection
- @connection.stubs(:add_index)
- @connection.create_table :delete_me, :force => true do |t|
- end
- end
-
- def teardown
- Person.connection.drop_table :delete_me rescue nil
- end
-
- def test_references_column_type_adds_id
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, 'customer_id', :integer, {})
- t.references :customer
- end
- end
-
- def test_remove_references_column_type_removes_id
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, 'customer_id')
- t.remove_references :customer
- end
- end
-
- def test_add_belongs_to_works_like_add_references
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, 'customer_id', :integer, {})
- t.belongs_to :customer
- end
- end
-
- def test_remove_belongs_to_works_like_remove_references
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, 'customer_id')
- t.remove_belongs_to :customer
- end
- end
-
- def test_references_column_type_with_polymorphic_adds_type
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, 'taggable_type', :string, {})
- @connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {})
- t.references :taggable, :polymorphic => true
- end
- end
-
- def test_remove_references_column_type_with_polymorphic_removes_type
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, 'taggable_type')
- @connection.expects(:remove_column).with(:delete_me, 'taggable_id')
- t.remove_references :taggable, :polymorphic => true
- end
- end
-
- def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, 'taggable_type', :string, {:null => false})
- @connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {:null => false})
- t.references :taggable, :polymorphic => true, :null => false
- end
- end
-
- def test_remove_references_column_type_with_polymorphic_and_options_null_is_false_removes_table_flag
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, 'taggable_type')
- @connection.expects(:remove_column).with(:delete_me, 'taggable_id')
- t.remove_references :taggable, :polymorphic => true, :null => false
- end
- end
-
- def test_timestamps_creates_updated_at_and_created_at
- with_change_table do |t|
- @connection.expects(:add_timestamps).with(:delete_me)
- t.timestamps
- end
- end
-
- def test_remove_timestamps_creates_updated_at_and_created_at
- with_change_table do |t|
- @connection.expects(:remove_timestamps).with(:delete_me)
- t.remove_timestamps
- end
- end
-
- def string_column
- if current_adapter?(:PostgreSQLAdapter)
- "character varying(255)"
- elsif current_adapter?(:OracleAdapter)
- 'VARCHAR2(255)'
- else
- 'varchar(255)'
- end
- end
-
- def integer_column
- if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
- 'int(11)'
- elsif current_adapter?(:OracleAdapter)
- 'NUMBER(38)'
- else
- 'integer'
- end
- end
-
- def test_integer_creates_integer_column
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, :foo, integer_column, {})
- @connection.expects(:add_column).with(:delete_me, :bar, integer_column, {})
- t.integer :foo, :bar
- end
- end
-
- def test_string_creates_string_column
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, :foo, string_column, {})
- @connection.expects(:add_column).with(:delete_me, :bar, string_column, {})
- t.string :foo, :bar
- end
- end
-
- def test_column_creates_column
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, :bar, :integer, {})
- t.column :bar, :integer
- end
- end
-
- def test_column_creates_column_with_options
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, :bar, :integer, {:null => false})
- t.column :bar, :integer, :null => false
- end
- end
-
- def test_index_creates_index
- with_change_table do |t|
- @connection.expects(:add_index).with(:delete_me, :bar, {})
- t.index :bar
- end
- end
-
- def test_index_creates_index_with_options
- with_change_table do |t|
- @connection.expects(:add_index).with(:delete_me, :bar, {:unique => true})
- t.index :bar, :unique => true
- end
- end
-
- def test_index_exists
- with_change_table do |t|
- @connection.expects(:index_exists?).with(:delete_me, :bar, {})
- t.index_exists?(:bar)
- end
- end
-
- def test_index_exists_with_options
- with_change_table do |t|
- @connection.expects(:index_exists?).with(:delete_me, :bar, {:unique => true})
- t.index_exists?(:bar, :unique => true)
- end
- end
-
- def test_change_changes_column
- with_change_table do |t|
- @connection.expects(:change_column).with(:delete_me, :bar, :string, {})
- t.change :bar, :string
- end
- end
-
- def test_change_changes_column_with_options
- with_change_table do |t|
- @connection.expects(:change_column).with(:delete_me, :bar, :string, {:null => true})
- t.change :bar, :string, :null => true
- end
- end
-
- def test_change_default_changes_column
- with_change_table do |t|
- @connection.expects(:change_column_default).with(:delete_me, :bar, :string)
- t.change_default :bar, :string
- end
- end
-
- def test_remove_drops_single_column
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, :bar)
- t.remove :bar
- end
- end
-
- def test_remove_drops_multiple_columns
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, :bar, :baz)
- t.remove :bar, :baz
- end
- end
-
- def test_remove_index_removes_index_with_options
- with_change_table do |t|
- @connection.expects(:remove_index).with(:delete_me, {:unique => true})
- t.remove_index :unique => true
- end
- end
-
- def test_rename_renames_column
- with_change_table do |t|
- @connection.expects(:rename_column).with(:delete_me, :bar, :baz)
- t.rename :bar, :baz
- end
- end
-
- protected
- def with_change_table
- Person.connection.change_table :delete_me do |t|
- yield t
- end
- end
-end
-
if ActiveRecord::Base.connection.supports_bulk_alter?
class BulkAlterTableMigrationsTest < ActiveRecord::TestCase
def setup
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index 2e87c5be2f..bf825c002a 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -272,7 +272,7 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_should_use_where_in_query_for_scope
- assert_equal Developer.find_all_by_name('Jamis').to_set, Developer.find_all_by_id(Developer.jamises).to_set
+ assert_equal Developer.where(name: 'Jamis').to_set, Developer.where(id: Developer.jamises).to_set
end
def test_size_should_use_count_when_results_are_not_loaded
@@ -439,40 +439,3 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal [posts(:welcome).title], klass.all.map(&:title)
end
end
-
-class DynamicScopeMatchTest < ActiveRecord::TestCase
- def test_scoped_by_no_match
- assert_nil ActiveRecord::DynamicMatchers::Method.match(nil, "not_scoped_at_all")
- end
-
- def test_scoped_by
- match = ActiveRecord::DynamicMatchers::Method.match(nil, "scoped_by_age_and_sex_and_location")
- assert_not_nil match
- assert_equal %w(age sex location), match.attribute_names
- end
-end
-
-class DynamicScopeTest < ActiveRecord::TestCase
- fixtures :posts
-
- def setup
- @test_klass = Class.new(Post) do
- def self.name; "Post"; end
- end
- end
-
- def test_dynamic_scope
- assert_equal @test_klass.scoped_by_author_id(1).find(1), @test_klass.find(1)
- assert_equal @test_klass.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, @test_klass.scoped(:where => { :author_id => 1, :title => "Welcome to the weblog"}).first
- end
-
- def test_dynamic_scope_should_create_methods_after_hitting_method_missing
- assert_blank @test_klass.methods.grep(/scoped_by_type/)
- @test_klass.scoped_by_type(nil)
- assert_present @test_klass.methods.grep(/scoped_by_type/)
- end
-
- def test_dynamic_scope_with_less_number_of_arguments
- assert_raise(ArgumentError){ @test_klass.scoped_by_author_id_and_title(1) }
- end
-end
diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb
index 39b66b3399..df0399f548 100644
--- a/activerecord/test/cases/readonly_test.rb
+++ b/activerecord/test/cases/readonly_test.rb
@@ -23,6 +23,7 @@ class ReadOnlyTest < ActiveRecord::TestCase
end
assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save }
assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save! }
+ assert_raise(ActiveRecord::ReadOnlyRecord) { dev.destroy }
end
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index 3462fd99bd..cf367242f2 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -254,11 +254,6 @@ class HasManyScopingTest< ActiveRecord::TestCase
assert_equal 2, @welcome.comments.search_by_type('Comment').size
end
- def test_forwarding_to_dynamic_finders
- assert_equal 4, Comment.find_all_by_type('Comment').size
- assert_equal 2, @welcome.comments.find_all_by_type('Comment').size
- end
-
def test_nested_scope_finder
Comment.where('1=0').scoping do
assert_equal 0, @welcome.comments.count
@@ -300,12 +295,6 @@ class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase
assert_equal 'a category...', @welcome.categories.what_are_you
end
- def test_forwarding_to_dynamic_finders
- assert_equal 4, Category.find_all_by_type('SpecialCategory').size
- assert_equal 0, @welcome.categories.find_all_by_type('SpecialCategory').size
- assert_equal 2, @welcome.categories.find_all_by_type('Category').size
- end
-
def test_nested_scope_finder
Category.where('1=0').scoping do
assert_equal 0, @welcome.categories.count
@@ -362,12 +351,12 @@ class DefaultScopingTest < ActiveRecord::TestCase
end
def test_default_scope_with_conditions_string
- assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.all.map(&:id).sort
+ assert_equal Developer.where(name: 'David').map(&:id).sort, DeveloperCalledDavid.all.map(&:id).sort
assert_equal nil, DeveloperCalledDavid.create!.name
end
def test_default_scope_with_conditions_hash
- assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.all.map(&:id).sort
+ assert_equal Developer.where(name: 'Jamis').map(&:id).sort, DeveloperCalledJamis.all.map(&:id).sort
assert_equal 'Jamis', DeveloperCalledJamis.create!.name
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 8a7a2441d4..89f818a689 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -203,13 +203,18 @@ module ActiveRecord
assert_equal [], relation.extending_values
end
- (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with]).each do |method|
+ (Relation::SINGLE_VALUE_METHODS - [:from, :lock, :reordering, :reverse_order, :create_with]).each do |method|
test "##{method}!" do
assert relation.public_send("#{method}!", :foo).equal?(relation)
assert_equal :foo, relation.public_send("#{method}_value")
end
end
+ test '#from!' do
+ assert relation.from!('foo').equal?(relation)
+ assert_equal ['foo', nil], relation.from_value
+ end
+
test '#lock!' do
assert relation.lock!('foo').equal?(relation)
assert_equal 'foo', relation.lock_value
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 76b868c7b4..4a56ae0d23 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -133,6 +133,13 @@ class RelationTest < ActiveRecord::TestCase
assert topics.loaded?
end
+ def test_finiding_with_subquery
+ relation = Topic.where(:approved => true)
+ assert_equal relation.to_a, Topic.select('*').from(relation).to_a
+ assert_equal relation.to_a, Topic.select('subquery.*').from(relation).to_a
+ assert_equal relation.to_a, Topic.select('a.*').from(relation, :a).to_a
+ end
+
def test_finding_with_conditions
assert_equal ["David"], Author.where(:name => 'David').map(&:name)
assert_equal ['Mary'], Author.where(["name = ?", 'Mary']).map(&:name)
@@ -401,18 +408,18 @@ class RelationTest < ActiveRecord::TestCase
end
def test_default_scope_with_conditions_string
- assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.scoped.map(&:id).sort
+ assert_equal Developer.where(name: 'David').map(&:id).sort, DeveloperCalledDavid.scoped.map(&:id).sort
assert_nil DeveloperCalledDavid.create!.name
end
def test_default_scope_with_conditions_hash
- assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.scoped.map(&:id).sort
+ assert_equal Developer.where(name: 'Jamis').map(&:id).sort, DeveloperCalledJamis.scoped.map(&:id).sort
assert_equal 'Jamis', DeveloperCalledJamis.create!.name
end
def test_default_scoping_finder_methods
developers = DeveloperCalledDavid.order('id').map(&:id).sort
- assert_equal Developer.find_all_by_name('David').map(&:id).sort, developers
+ assert_equal Developer.where(name: 'David').map(&:id).sort, developers
end
def test_loading_with_one_association
@@ -436,15 +443,6 @@ class RelationTest < ActiveRecord::TestCase
assert_equal Post.find(1).last_comment, post.last_comment
end
- def test_dynamic_find_by_attributes_should_yield_found_object
- david = authors(:david)
- yielded_value = nil
- Author.find_by_name(david.name) do |author|
- yielded_value = author
- end
- assert_equal david, yielded_value
- end
-
def test_dynamic_find_by_attributes
david = authors(:david)
author = Author.preload(:taggings).find_by_id(david.id)
@@ -466,46 +464,6 @@ class RelationTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::RecordNotFound) { Author.scoped.find_by_id_and_name!(20, 'invalid') }
end
- def test_dynamic_find_all_by_attributes
- authors = Author.scoped
-
- davids = authors.find_all_by_name('David')
- assert_kind_of Array, davids
- assert_equal [authors(:david)], davids
- end
-
- def test_dynamic_find_or_initialize_by_attributes
- authors = Author.scoped
-
- lifo = authors.find_or_initialize_by_name('Lifo')
- assert_equal "Lifo", lifo.name
- assert !lifo.persisted?
-
- assert_equal authors(:david), authors.find_or_initialize_by_name(:name => 'David')
- end
-
- def test_dynamic_find_or_create_by_attributes
- authors = Author.scoped
-
- lifo = authors.find_or_create_by_name('Lifo')
- assert_equal "Lifo", lifo.name
- assert lifo.persisted?
-
- assert_equal authors(:david), authors.find_or_create_by_name(:name => 'David')
- end
-
- def test_dynamic_find_or_create_by_attributes_bang
- authors = Author.scoped
-
- assert_raises(ActiveRecord::RecordInvalid) { authors.find_or_create_by_name!('') }
-
- lifo = authors.find_or_create_by_name!('Lifo')
- assert_equal "Lifo", lifo.name
- assert lifo.persisted?
-
- assert_equal authors(:david), authors.find_or_create_by_name!(:name => 'David')
- end
-
def test_find_id
authors = Author.scoped
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 3fc58c25b1..5bcb9652cd 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -80,6 +80,7 @@ ActiveRecord::Schema.define do
create_table :binaries, :force => true do |t|
t.string :name
t.binary :data
+ t.binary :short_data, :limit => 2048
end
create_table :birds, :force => true do |t|
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 0aa3efbb63..a9253c186d 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -328,26 +328,17 @@ module ActiveSupport
# if it was not yet defined.
# This generated method plays caching role.
def __define_callbacks(kind, object) #:nodoc:
- name = __callback_runner_name(kind)
+ chain = object.send("_#{kind}_callbacks")
+ name = "_run_callbacks_#{chain.object_id.abs}"
unless object.respond_to?(name, true)
- str = object.send("_#{kind}_callbacks").compile
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{name}() #{str} end
+ def #{name}() #{chain.compile} end
protected :#{name}
RUBY_EVAL
end
name
end
- def __reset_runner(symbol)
- name = __callback_runner_name(symbol)
- undef_method(name) if method_defined?(name)
- end
-
- def __callback_runner_name(kind)
- "_run__#{self.name.hash.abs}__#{kind}__callbacks"
- end
-
# This is used internally to append, prepend and skip callbacks to the
# CallbackChain.
#
@@ -359,7 +350,6 @@ module ActiveSupport
([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse.each do |target|
chain = target.send("_#{name}_callbacks")
yield target, chain.dup, type, filters, options
- target.__reset_runner(name)
end
end
@@ -447,12 +437,9 @@ module ActiveSupport
chain = target.send("_#{symbol}_callbacks").dup
callbacks.each { |c| chain.delete(c) }
target.send("_#{symbol}_callbacks=", chain)
- target.__reset_runner(symbol)
end
self.send("_#{symbol}_callbacks=", callbacks.dup.clear)
-
- __reset_runner(symbol)
end
# Define sets of events in the object lifecycle that support callbacks.
diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb
index dd63b64c77..f1b755c2c4 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -19,7 +19,7 @@
class Object
# Can you safely dup this object?
#
- # False for +nil+, +false+, +true+, symbols, numbers, class and module objects;
+ # False for +nil+, +false+, +true+, symbol, and number objects;
# true otherwise.
def duplicable?
true
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 f98d5b3777..5226ff0cbe 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -151,9 +151,7 @@ module ActiveSupport #:nodoc:
end
def %(args)
- args = Array(args)
-
- args.map! do |arg|
+ args = Array(args).map do |arg|
if !html_safe? || arg.html_safe?
arg
else
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index a0f610d60c..92b8417150 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -253,7 +253,7 @@ class Time
:hour => 23,
:min => 59,
:sec => 59,
- :usec => 999999.999
+ :usec => Rational(999999999, 1000)
)
end
@@ -268,7 +268,7 @@ class Time
change(
:min => 59,
:sec => 59,
- :usec => 999999.999
+ :usec => Rational(999999999, 1000)
)
end
@@ -288,7 +288,7 @@ class Time
:hour => 23,
:min => 59,
:sec => 59,
- :usec => 999999.999
+ :usec => Rational(999999999, 1000)
)
end
alias :at_end_of_month :end_of_month
@@ -321,7 +321,7 @@ class Time
:hour => 23,
:min => 59,
:sec => 59,
- :usec => 999999.999
+ :usec => Rational(999999999, 1000)
)
end
alias :at_end_of_year :end_of_year
diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb
index 4f852fd780..10ca26acf2 100644
--- a/activesupport/lib/active_support/core_ext/time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/time/conversions.rb
@@ -5,6 +5,7 @@ class Time
DATE_FORMATS = {
:db => '%Y-%m-%d %H:%M:%S',
:number => '%Y%m%d%H%M%S',
+ :nsec => '%Y%m%d%H%M%S%9N',
:time => '%H:%M',
:short => '%d %b %H:%M',
:long => '%B %d, %Y %H:%M',
diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb
index 9d01dc85c0..b65ea6208c 100644
--- a/activesupport/lib/active_support/log_subscriber/test_helper.rb
+++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb
@@ -61,7 +61,7 @@ module ActiveSupport
@logged = Hash.new { |h,k| h[k] = [] }
end
- def method_missing(level, message)
+ def method_missing(level, message = nil)
if block_given?
@logged[level] << yield
else
diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb
index ec6986654e..2bea0f991a 100644
--- a/activesupport/lib/active_support/testing/performance.rb
+++ b/activesupport/lib/active_support/testing/performance.rb
@@ -196,6 +196,7 @@ module ActiveSupport
class Base
include ActionView::Helpers::NumberHelper
+ include ActionView::Helpers::OutputSafetyHelper
attr_reader :total
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 67ac1b6ccd..451520ac5c 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -317,8 +317,7 @@ module ActiveSupport
# Send the missing method to +time+ instance, and wrap result in a new TimeWithZone with the existing +time_zone+.
def method_missing(sym, *args, &block)
- result = time.__send__(sym, *args, &block)
- result.acts_like?(:time) ? self.class.new(nil, time_zone, result) : result
+ wrap_with_time_zone time.__send__(sym, *args, &block)
end
private
@@ -336,11 +335,21 @@ module ActiveSupport
end
def transfer_time_values_to_utc_constructor(time)
- ::Time.utc_time(time.year, time.month, time.day, time.hour, time.min, time.sec, time.respond_to?(:usec) ? time.usec : 0)
+ ::Time.utc_time(time.year, time.month, time.day, time.hour, time.min, time.sec, time.respond_to?(:nsec) ? Rational(time.nsec, 1000) : 0)
end
def duration_of_variable_length?(obj)
ActiveSupport::Duration === obj && obj.parts.any? {|p| p[0].in?([:years, :months, :days]) }
end
+
+ def wrap_with_time_zone(time)
+ if time.acts_like?(:time)
+ self.class.new(nil, time_zone, time)
+ elsif time.is_a?(Range)
+ wrap_with_time_zone(time.begin)..wrap_with_time_zone(time.end)
+ else
+ time
+ end
+ end
end
end
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index 760d138623..e14a137f84 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -374,14 +374,14 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
end
def test_end_of_day
- assert_equal Time.local(2005,2,21,23,59,59,999999.999), Date.new(2005,2,21).end_of_day
+ assert_equal Time.local(2005,2,21,23,59,59,Rational(999999999, 1000)), Date.new(2005,2,21).end_of_day
end
def test_end_of_day_when_zone_is_set
zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
with_env_tz 'UTC' do
with_tz_default zone do
- assert_equal zone.local(2005,2,21,23,59,59,999999.999), Date.new(2005,2,21).end_of_day
+ assert_equal zone.local(2005,2,21,23,59,59,Rational(999999999, 1000)), Date.new(2005,2,21).end_of_day
assert_equal zone, Date.new(2005,2,21).end_of_day.time_zone
end
end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index eee2caa60e..8437ef1347 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -463,6 +463,13 @@ class OutputSafetyTest < ActiveSupport::TestCase
assert @other_string.html_safe?
end
+ test "Concatting with % doesn't modify a string" do
+ @other_string = ["<p>", "<b>", "<h1>"]
+ _ = "%s %s %s".html_safe % @other_string
+
+ assert_equal ["<p>", "<b>", "<h1>"], @other_string
+ end
+
test "Concatting a fixnum to safe always yields safe" do
string = @string.html_safe
string = string.concat(13)
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index 4c1ed4b1ae..15c04bedf7 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -109,49 +109,49 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_end_of_day
- assert_equal Time.local(2007,8,12,23,59,59,999999.999), Time.local(2007,8,12,10,10,10).end_of_day
+ assert_equal Time.local(2007,8,12,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,12,10,10,10).end_of_day
with_env_tz 'US/Eastern' do
- assert_equal Time.local(2007,4,2,23,59,59,999999.999), Time.local(2007,4,2,10,10,10).end_of_day, 'start DST'
- assert_equal Time.local(2007,10,29,23,59,59,999999.999), Time.local(2007,10,29,10,10,10).end_of_day, 'ends DST'
+ assert_equal Time.local(2007,4,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,4,2,10,10,10).end_of_day, 'start DST'
+ assert_equal Time.local(2007,10,29,23,59,59,Rational(999999999, 1000)), Time.local(2007,10,29,10,10,10).end_of_day, 'ends DST'
end
with_env_tz 'NZ' do
- assert_equal Time.local(2006,3,19,23,59,59,999999.999), Time.local(2006,3,19,10,10,10).end_of_day, 'ends DST'
- assert_equal Time.local(2006,10,1,23,59,59,999999.999), Time.local(2006,10,1,10,10,10).end_of_day, 'start DST'
+ assert_equal Time.local(2006,3,19,23,59,59,Rational(999999999, 1000)), Time.local(2006,3,19,10,10,10).end_of_day, 'ends DST'
+ assert_equal Time.local(2006,10,1,23,59,59,Rational(999999999, 1000)), Time.local(2006,10,1,10,10,10).end_of_day, 'start DST'
end
end
def test_end_of_week
- assert_equal Time.local(2008,1,6,23,59,59,999999.999), Time.local(2007,12,31,10,10,10).end_of_week
- assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,27,0,0,0).end_of_week #monday
- assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,28,0,0,0).end_of_week #tuesday
- assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,29,0,0,0).end_of_week #wednesday
- assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,30,0,0,0).end_of_week #thursday
- assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,31,0,0,0).end_of_week #friday
- assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,9,01,0,0,0).end_of_week #saturday
- assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,9,02,0,0,0).end_of_week #sunday
+ assert_equal Time.local(2008,1,6,23,59,59,Rational(999999999, 1000)), Time.local(2007,12,31,10,10,10).end_of_week
+ assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,27,0,0,0).end_of_week #monday
+ assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,28,0,0,0).end_of_week #tuesday
+ assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,29,0,0,0).end_of_week #wednesday
+ assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,30,0,0,0).end_of_week #thursday
+ assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,31,0,0,0).end_of_week #friday
+ assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,9,01,0,0,0).end_of_week #saturday
+ assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,9,02,0,0,0).end_of_week #sunday
end
def test_end_of_hour
- assert_equal Time.local(2005,2,4,19,59,59,999999.999), Time.local(2005,2,4,19,30,10).end_of_hour
+ assert_equal Time.local(2005,2,4,19,59,59,Rational(999999999, 1000)), Time.local(2005,2,4,19,30,10).end_of_hour
end
def test_end_of_month
- assert_equal Time.local(2005,3,31,23,59,59,999999.999), Time.local(2005,3,20,10,10,10).end_of_month
- assert_equal Time.local(2005,2,28,23,59,59,999999.999), Time.local(2005,2,20,10,10,10).end_of_month
- assert_equal Time.local(2005,4,30,23,59,59,999999.999), Time.local(2005,4,20,10,10,10).end_of_month
+ assert_equal Time.local(2005,3,31,23,59,59,Rational(999999999, 1000)), Time.local(2005,3,20,10,10,10).end_of_month
+ assert_equal Time.local(2005,2,28,23,59,59,Rational(999999999, 1000)), Time.local(2005,2,20,10,10,10).end_of_month
+ assert_equal Time.local(2005,4,30,23,59,59,Rational(999999999, 1000)), Time.local(2005,4,20,10,10,10).end_of_month
end
def test_end_of_quarter
- assert_equal Time.local(2007,3,31,23,59,59,999999.999), Time.local(2007,2,15,10,10,10).end_of_quarter
- assert_equal Time.local(2007,3,31,23,59,59,999999.999), Time.local(2007,3,31,0,0,0).end_of_quarter
- assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,12,21,10,10,10).end_of_quarter
- assert_equal Time.local(2007,6,30,23,59,59,999999.999), Time.local(2007,4,1,0,0,0).end_of_quarter
- assert_equal Time.local(2008,6,30,23,59,59,999999.999), Time.local(2008,5,31,0,0,0).end_of_quarter
+ assert_equal Time.local(2007,3,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,2,15,10,10,10).end_of_quarter
+ assert_equal Time.local(2007,3,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,3,31,0,0,0).end_of_quarter
+ assert_equal Time.local(2007,12,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,12,21,10,10,10).end_of_quarter
+ assert_equal Time.local(2007,6,30,23,59,59,Rational(999999999, 1000)), Time.local(2007,4,1,0,0,0).end_of_quarter
+ assert_equal Time.local(2008,6,30,23,59,59,Rational(999999999, 1000)), Time.local(2008,5,31,0,0,0).end_of_quarter
end
def test_end_of_year
- assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,2,22,10,10,10).end_of_year
- assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,12,31,10,10,10).end_of_year
+ assert_equal Time.local(2007,12,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,2,22,10,10,10).end_of_year
+ assert_equal Time.local(2007,12,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,12,31,10,10,10).end_of_year
end
def test_beginning_of_year
@@ -517,7 +517,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.local(2006,11,15), Time.local(2006,11,23,0,0,0).prev_week(:wednesday)
end
end
-
+
def test_last_week
with_env_tz 'US/Eastern' do
assert_equal Time.local(2005,2,21), Time.local(2005,3,1,15,15,10).last_week
@@ -557,12 +557,14 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_to_s
- time = Time.utc(2005, 2, 21, 17, 44, 30)
+ time = Time.utc(2005, 2, 21, 17, 44, 30.12345678901)
assert_equal time.to_default_s, time.to_s
assert_equal time.to_default_s, time.to_s(:doesnt_exist)
assert_equal "2005-02-21 17:44:30", time.to_s(:db)
assert_equal "21 Feb 17:44", time.to_s(:short)
assert_equal "17:44", time.to_s(:time)
+ assert_equal "20050221174430", time.to_s(:number)
+ assert_equal "20050221174430123456789", time.to_s(:nsec)
assert_equal "February 21, 2005 17:44", time.to_s(:long)
assert_equal "February 21st, 2005 17:44", time.to_s(:long_ordinal)
with_env_tz "UTC" do
@@ -828,24 +830,32 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
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
+ assert_equal Time.local(2011,6,7,0,0,0)..Time.local(2011,6,7,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_day
+ end
+
+ def test_all_day_with_timezone
+ beginning_of_day = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], Time.local(2011,6,7,0,0,0))
+ end_of_day = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], Time.local(2011,6,7,23,59,59,Rational(999999999, 1000)))
+
+ assert_equal beginning_of_day, ActiveSupport::TimeWithZone.new(Time.local(2011,6,7,10,10,10), ActiveSupport::TimeZone["Hawaii"]).all_day.begin
+ assert_equal end_of_day, ActiveSupport::TimeWithZone.new(Time.local(2011,6,7,10,10,10), ActiveSupport::TimeZone["Hawaii"]).all_day.end
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
- assert_equal Time.local(2011,6,5,0,0,0)..Time.local(2011,6,11,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_week(:sunday)
+ assert_equal Time.local(2011,6,6,0,0,0)..Time.local(2011,6,12,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_week
+ assert_equal Time.local(2011,6,5,0,0,0)..Time.local(2011,6,11,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_week(:sunday)
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
+ assert_equal Time.local(2011,6,1,0,0,0)..Time.local(2011,6,30,23,59,59,Rational(999999999, 1000)), 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
+ assert_equal Time.local(2011,4,1,0,0,0)..Time.local(2011,6,30,23,59,59,Rational(999999999, 1000)), 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
+ assert_equal Time.local(2011,1,1,0,0,0)..Time.local(2011,12,31,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_year
end
protected
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index b62337e31b..1293f104e5 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -80,6 +80,14 @@ class TimeWithZoneTest < ActiveSupport::TestCase
ActiveSupport.use_standard_json_time_format = old
end
+ def test_nsec
+ local = Time.local(2011,6,7,23,59,59,Rational(999999999, 1000))
+ with_zone = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], local)
+
+ assert_equal local.nsec, with_zone.nsec
+ assert_equal with_zone.nsec, 999999999
+ end
+
def test_strftime
assert_equal '1999-12-31 19:00:00 EST -0500', @twz.strftime('%Y-%m-%d %H:%M:%S %Z %z')
end
diff --git a/guides/source/form_helpers.textile b/guides/source/form_helpers.textile
index 711ed3d859..033b33ec3b 100644
--- a/guides/source/form_helpers.textile
+++ b/guides/source/form_helpers.textile
@@ -150,7 +150,7 @@ NOTE: Always use labels for checkbox and radio buttons. They associate text with
h4. Other Helpers of Interest
-Other form controls worth mentioning are textareas, password fields, hidden fields, search fields, telephone fields, date fields, URL fields and email fields:
+Other form controls worth mentioning are textareas, password fields, hidden fields, search fields, telephone fields, date fields, time fields, URL fields and email fields:
<erb>
<%= text_area_tag(:message, "Hi, nice site", :size => "24x6") %>
@@ -161,6 +161,7 @@ Other form controls worth mentioning are textareas, password fields, hidden fiel
<%= date_field(:user, :born_on) %>
<%= url_field(:user, :homepage) %>
<%= email_field(:user, :address) %>
+<%= time_field(:task, :started_at) %>
</erb>
Output:
@@ -174,11 +175,12 @@ Output:
<input id="user_born_on" name="user[born_on]" type="date" />
<input id="user_homepage" name="user[homepage]" type="url" />
<input id="user_address" name="user[address]" type="email" />
+<input id="task_started_at" name="task[started_at]" type="time" />
</html>
Hidden inputs are not shown to the user but instead hold data like any textual input. Values inside them can be changed with JavaScript.
-IMPORTANT: The search, telephone, date, URL, and email inputs are HTML5 controls. If you require your app to have a consistent experience in older browsers, you will need an HTML5 polyfill (provided by CSS and/or JavaScript). There is definitely "no shortage of solutions for this":https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills, although a couple of popular tools at the moment are "Modernizr":http://www.modernizr.com/ and "yepnope":http://yepnopejs.com/, which provide a simple way to add functionality based on the presence of detected HTML5 features.
+IMPORTANT: The search, telephone, date, time, URL, and email inputs are HTML5 controls. If you require your app to have a consistent experience in older browsers, you will need an HTML5 polyfill (provided by CSS and/or JavaScript). There is definitely "no shortage of solutions for this":https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills, although a couple of popular tools at the moment are "Modernizr":http://www.modernizr.com/ and "yepnope":http://yepnopejs.com/, which provide a simple way to add functionality based on the presence of detected HTML5 features.
TIP: If you're using password input fields (for any purpose), you might want to configure your application to prevent those parameters from being logged. You can learn about this in the "Security Guide":security.html#logging.
@@ -471,7 +473,7 @@ Rails _used_ to have a +country_select+ helper for choosing countries, but this
h3. Using Date and Time Form Helpers
-You can choose not to use the form helpers generating HTML5 date input fields and use the alternative date and time helpers. These date and time helpers differ from all the other form helpers in two important respects:
+You can choose not to use the form helpers generating HTML5 date and time input fields and use the alternative date and time helpers. These date and time helpers differ from all the other form helpers in two important respects:
# Dates and times are not representable by a single input element. Instead you have several, one for each component (year, month, day etc.) and so there is no single value in your +params+ hash with your date or time.
# Other helpers use the +_tag+ suffix to indicate whether a helper is a barebones helper or one that operates on model objects. With dates and times, +select_date+, +select_time+ and +select_datetime+ are the barebones helpers, +date_select+, +time_select+ and +datetime_select+ are the equivalent model object helpers.
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 1f88843ee9..ccccc178c5 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,5 +1,7 @@
## Rails 4.0.0 (unreleased) ##
+* Improved `rake routes` output for redirects *Łukasz Strzałkowski & Andrew White*
+
* Load all environments available in `config.paths["config/environments"]`. *Piotr Sarnacki*
* The application generator generates `public/humans.txt` with some basic data. *Paul Campbell*
diff --git a/railties/lib/rails/application/route_inspector.rb b/railties/lib/rails/application/route_inspector.rb
index 1e5ce67a58..b23fb3e920 100644
--- a/railties/lib/rails/application/route_inspector.rb
+++ b/railties/lib/rails/application/route_inspector.rb
@@ -16,7 +16,7 @@ module Rails
class_name = app.class.name.to_s
if class_name == "ActionDispatch::Routing::Mapper::Constraints"
rack_app(app.app)
- elsif class_name !~ /^ActionDispatch::Routing/
+ elsif ActionDispatch::Routing::Redirect === app || class_name !~ /^ActionDispatch::Routing/
app
end
end
@@ -67,7 +67,7 @@ module Rails
@engines = Hash.new
end
- def format all_routes, filter = nil
+ def format(all_routes, filter = nil)
if filter
all_routes = all_routes.select{ |route| route.defaults[:controller] == filter }
end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 10ad475d55..2c1742c6be 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -246,17 +246,8 @@ module Rails
# is easier to silence stdout in the existing test suite this way. The
# end-user gets the bundler commands called anyway, so no big deal.
#
- # We unset temporary bundler variables to load proper bundler and Gemfile.
- #
# Thanks to James Tucker for the Gem tricks involved in this call.
- _bundle_command = Gem.bin_path('bundler', 'bundle')
-
- bundle_bin_path, bundle_gemfile, rubyopt = ENV['BUNDLE_BIN_PATH'], ENV['BUNDLE_GEMFILE'], ENV['RUBYOPT']
- ENV['BUNDLE_BIN_PATH'], ENV['BUNDLE_GEMFILE'], ENV['RUBYOPT'] = "", "", ""
-
- print `"#{Gem.ruby}" "#{_bundle_command}" #{command}`
-
- ENV['BUNDLE_BIN_PATH'], ENV['BUNDLE_GEMFILE'], ENV['RUBYOPT'] = bundle_bin_path, bundle_gemfile, rubyopt
+ print `"#{Gem.ruby}" "#{Gem.bin_path('bundler', 'bundle')}" #{command}`
end
def run_bundle
diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb
index 508e221c60..ff9cf0087e 100644
--- a/railties/lib/rails/generators/test_case.rb
+++ b/railties/lib/rails/generators/test_case.rb
@@ -31,7 +31,6 @@ module Rails
include FileUtils
class_attribute :destination_root, :current_path, :generator_class, :default_arguments
- delegate :destination_root, :current_path, :generator_class, :default_arguments, :to => :'self.class'
# Generators frequently change the current path using +FileUtils.cd+.
# So we need to store the path at file load and revert back to it after each test.
diff --git a/railties/test/application/route_inspect_test.rb b/railties/test/application/route_inspect_test.rb
index 453ba8196c..3b8c874b5b 100644
--- a/railties/test/application/route_inspect_test.rb
+++ b/railties/test/application/route_inspect_test.rb
@@ -16,6 +16,11 @@ module ApplicationTests
Rails.stubs(:env).returns("development")
end
+ def draw(&block)
+ @set.draw(&block)
+ @inspector.format(@set.routes)
+ end
+
def test_displaying_routes_for_engines
engine = Class.new(Rails::Engine) do
def self.to_s
@@ -26,12 +31,11 @@ module ApplicationTests
get '/cart', :to => 'cart#show'
end
- @set.draw do
+ output = draw do
get '/custom/assets', :to => 'custom_assets#show'
mount engine => "/blog", :as => "blog"
end
- output = @inspector.format @set.routes
expected = [
"custom_assets GET /custom/assets(.:format) custom_assets#show",
" blog /blog Blog::Engine",
@@ -42,26 +46,23 @@ module ApplicationTests
end
def test_cart_inspect
- @set.draw do
+ output = draw do
get '/cart', :to => 'cart#show'
end
- output = @inspector.format @set.routes
assert_equal ["cart GET /cart(.:format) cart#show"], output
end
def test_inspect_shows_custom_assets
- @set.draw do
+ output = draw do
get '/custom/assets', :to => 'custom_assets#show'
end
- output = @inspector.format @set.routes
assert_equal ["custom_assets GET /custom/assets(.:format) custom_assets#show"], output
end
def test_inspect_routes_shows_resources_route
- @set.draw do
+ output = draw do
resources :articles
end
- output = @inspector.format @set.routes
expected = [
" articles GET /articles(.:format) articles#index",
" POST /articles(.:format) articles#create",
@@ -75,50 +76,44 @@ module ApplicationTests
end
def test_inspect_routes_shows_root_route
- @set.draw do
+ output = draw do
root :to => 'pages#main'
end
- output = @inspector.format @set.routes
assert_equal ["root GET / pages#main"], output
end
def test_inspect_routes_shows_dynamic_action_route
- @set.draw do
+ output = draw do
get 'api/:action' => 'api'
end
- output = @inspector.format @set.routes
assert_equal [" GET /api/:action(.:format) api#:action"], output
end
def test_inspect_routes_shows_controller_and_action_only_route
- @set.draw do
+ output = draw do
get ':controller/:action'
end
- output = @inspector.format @set.routes
assert_equal [" GET /:controller/:action(.:format) :controller#:action"], output
end
def test_inspect_routes_shows_controller_and_action_route_with_constraints
- @set.draw do
+ output = draw do
get ':controller(/:action(/:id))', :id => /\d+/
end
- output = @inspector.format @set.routes
assert_equal [" GET /:controller(/:action(/:id))(.:format) :controller#:action {:id=>/\\d+/}"], output
end
def test_rake_routes_shows_route_with_defaults
- @set.draw do
+ output = draw do
get 'photos/:id' => 'photos#show', :defaults => {:format => 'jpg'}
end
- output = @inspector.format @set.routes
assert_equal [%Q[ GET /photos/:id(.:format) photos#show {:format=>"jpg"}]], output
end
def test_rake_routes_shows_route_with_constraints
- @set.draw do
+ output = draw do
get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
end
- output = @inspector.format @set.routes
assert_equal [" GET /photos/:id(.:format) photos#show {:id=>/[A-Z]\\d{5}/}"], output
end
@@ -128,10 +123,9 @@ module ApplicationTests
end
def test_rake_routes_shows_route_with_rack_app
- @set.draw do
+ output = draw do
get 'foo/:id' => RackApp, :id => /[A-Z]\d{5}/
end
- output = @inspector.format @set.routes
assert_equal [" GET /foo/:id(.:format) #{RackApp.name} {:id=>/[A-Z]\\d{5}/}"], output
end
@@ -142,23 +136,33 @@ module ApplicationTests
end
end
- @set.draw do
+ output = draw do
scope :constraint => constraint.new do
mount RackApp => '/foo'
end
end
- output = @inspector.format @set.routes
assert_equal [" /foo #{RackApp.name} {:constraint=>( my custom constraint )}"], output
end
def test_rake_routes_dont_show_app_mounted_in_assets_prefix
- @set.draw do
+ output = draw do
get '/sprockets' => RackApp
end
- output = @inspector.format @set.routes
assert_no_match(/RackApp/, output.first)
assert_no_match(/\/sprockets/, output.first)
end
+
+ def test_redirect
+ output = draw do
+ get "/foo" => redirect("/foo/bar"), :constraints => { :subdomain => "admin" }
+ get "/bar" => redirect(path: "/foo/bar", status: 307)
+ get "/foobar" => redirect{ "/foo/bar" }
+ end
+
+ assert_equal " foo GET /foo(.:format) redirect(301, /foo/bar) {:subdomain=>\"admin\"}", output[0]
+ assert_equal " bar GET /bar(.:format) redirect(307, path: /foo/bar)", output[1]
+ assert_equal "foobar GET /foobar(.:format) redirect(301)", output[2]
+ end
end
end
diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb
index 6f00fc5d26..c495258343 100644
--- a/railties/test/generators/namespaced_generators_test.rb
+++ b/railties/test/generators/namespaced_generators_test.rb
@@ -66,7 +66,7 @@ class NamespacedControllerGeneratorTest < NamespacedGeneratorTestCase
run_generator
assert_file "app/controllers/test_app/account_controller.rb" do |content|
content.split("\n").each do |line|
- assert_no_match line, /^\s+$/, "Don't indent blank lines"
+ assert_no_match(/^\s+$/, line, "Don't indent blank lines")
end
end
end
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index b0e1865b26..51374e5f3f 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -99,13 +99,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
end
def test_generation_runs_bundle_install_with_full_and_mountable
- result = run_generator [destination_root, "--mountable", "--full", "--dev"]
- assert_file "#{destination_root}/Gemfile.lock" do |contents|
- assert_match(/bukkits/, contents)
- end
- assert_match(/run bundle install/, result)
- assert_match(/Using bukkits \(0\.0\.1\)/, result)
- assert_match(/Your bundle is complete/, result)
+ result = run_generator [destination_root, "--mountable", "--full"]
assert_equal 1, result.scan("Your bundle is complete").size
end
@@ -354,3 +348,4 @@ protected
silence(:stdout){ generator.send(*args, &block) }
end
end
+