aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb66
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb2
-rw-r--r--actionpack/test/dispatch/routing_test.rb18
-rw-r--r--actionpack/test/template/form_helper_test.rb5
-rw-r--r--activemodel/lib/active_model/mass_assignment_security/permission_set.rb3
-rw-r--r--activemodel/test/cases/mass_assignment_security/sanitizer_test.rb2
-rw-r--r--activemodel/test/cases/mass_assignment_security_test.rb6
-rw-r--r--activerecord/lib/active_record/associations/through_association_scope.rb2
-rw-r--r--activerecord/lib/active_record/base.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb60
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb27
-rw-r--r--activerecord/lib/active_record/persistence.rb15
-rw-r--r--activerecord/lib/active_record/railtie.rb13
-rw-r--r--activerecord/lib/active_record/relation.rb4
-rw-r--r--activerecord/lib/active_record/timestamp.rb36
-rw-r--r--activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb118
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb12
-rw-r--r--activerecord/test/cases/json_serialization_test.rb7
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb6
-rw-r--r--activerecord/test/cases/timestamp_test.rb10
-rw-r--r--activerecord/test/fixtures/dashboards.yml3
-rw-r--r--activerecord/test/fixtures/minivans.yml4
-rw-r--r--activerecord/test/fixtures/speedometers.yml4
-rw-r--r--activerecord/test/models/dashboard.rb3
-rw-r--r--activerecord/test/models/developer.rb1
-rw-r--r--activerecord/test/models/minivan.rb6
-rw-r--r--activerecord/test/models/speedometer.rb4
-rw-r--r--activerecord/test/schema/schema.rb17
-rw-r--r--activesupport/test/notifications_test.rb6
-rwxr-xr-xci/ci_build.rb6
-rw-r--r--rails.gemspec2
-rw-r--r--railties/test/application/initializers/frameworks_test.rb12
-rw-r--r--railties/test/application/middleware_test.rb1
35 files changed, 341 insertions, 152 deletions
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 430f6fc5a3..526c97ff8e 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -35,11 +35,12 @@ module ActionDispatch
end
class Mapping #:nodoc:
- IGNORE_OPTIONS = [:to, :as, :controller, :action, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix]
+ IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix]
def initialize(set, scope, args)
@set, @scope = set, scope
@path, @options = extract_path_and_options(args)
+ normalize_options!
end
def to_route
@@ -57,14 +58,6 @@ module ActionDispatch
path = args.first
end
- if @scope[:module] && options[:to] && !options[:to].is_a?(Proc)
- if options[:to].to_s.include?("#")
- options[:to] = "#{@scope[:module]}/#{options[:to]}"
- elsif @scope[:controller].nil?
- options[:to] = "#{@scope[:module]}##{options[:to]}"
- end
- end
-
if path.match(':controller')
raise ArgumentError, ":controller segment is not allowed within a namespace block" if @scope[:module]
@@ -75,15 +68,19 @@ module ActionDispatch
options.reverse_merge!(:controller => /.+?/)
end
- path = normalize_path(path)
- path_without_format = path.sub(/\(\.:format\)$/, '')
+ [ normalize_path(path), options ]
+ end
+
+ def normalize_options!
+ path_without_format = @path.sub(/\(\.:format\)$/, '')
- if using_match_shorthand?(path_without_format, options)
- options[:to] ||= path_without_format[1..-1].sub(%r{/([^/]*)$}, '#\1')
- options[:as] ||= path_without_format[1..-1].gsub("/", "_")
+ if using_match_shorthand?(path_without_format, @options)
+ to_shorthand = @options[:to].blank?
+ @options[:to] ||= path_without_format[1..-1].sub(%r{/([^/]*)$}, '#\1')
+ @options[:as] ||= path_without_format[1..-1].gsub("/", "_")
end
- [ path, options ]
+ @options.merge!(default_controller_and_action(to_shorthand))
end
# match "account" => "account#index"
@@ -122,44 +119,43 @@ module ActionDispatch
def defaults
@defaults ||= (@options[:defaults] || {}).tap do |defaults|
- defaults.merge!(default_controller_and_action)
defaults.reverse_merge!(@scope[:defaults]) if @scope[:defaults]
@options.each { |k, v| defaults[k] = v unless v.is_a?(Regexp) || IGNORE_OPTIONS.include?(k.to_sym) }
end
end
- def default_controller_and_action
+ def default_controller_and_action(to_shorthand=nil)
if to.respond_to?(:call)
{ }
else
- defaults = case to
- when String
+ if to.is_a?(String)
controller, action = to.split('#')
- { :controller => controller, :action => action }
- when Symbol
- { :action => to.to_s }
- else
- {}
+ elsif to.is_a?(Symbol)
+ action = to.to_s
end
- defaults[:controller] ||= default_controller
- defaults[:action] ||= default_action
+ controller ||= default_controller
+ action ||= default_action
- defaults.delete(:controller) if defaults[:controller].blank? || defaults[:controller].is_a?(Regexp)
- defaults.delete(:action) if defaults[:action].blank? || defaults[:action].is_a?(Regexp)
+ unless controller.is_a?(Regexp) || to_shorthand
+ controller = [@scope[:module], controller].compact.join("/").presence
+ end
- defaults[:controller] = defaults[:controller].to_s if defaults.key?(:controller)
- defaults[:action] = defaults[:action].to_s if defaults.key?(:action)
+ controller = controller.to_s unless controller.is_a?(Regexp)
+ action = action.to_s unless action.is_a?(Regexp)
- if defaults[:controller].blank? && segment_keys.exclude?("controller")
+ if controller.blank? && segment_keys.exclude?("controller")
raise ArgumentError, "missing :controller"
end
- if defaults[:action].blank? && segment_keys.exclude?("action")
+ if action.blank? && segment_keys.exclude?("action")
raise ArgumentError, "missing :action"
end
- defaults
+ { :controller => controller, :action => action }.tap do |hash|
+ hash.delete(:controller) if hash[:controller].blank?
+ hash.delete(:action) if hash[:action].blank?
+ end
end
end
@@ -207,6 +203,8 @@ module ActionDispatch
def default_action
if @options[:action]
@options[:action]
+ elsif @scope[:action]
+ @scope[:action]
end
end
end
@@ -424,7 +422,7 @@ module ActionDispatch
end
def merge_controller_scope(parent, child)
- @scope[:module] ? "#{@scope[:module]}/#{child}" : child
+ child
end
def merge_path_names_scope(parent, child)
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 6302491c2a..711c455ded 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -676,7 +676,7 @@ module ActionView
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
#
def file_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options.update({:size => nil}))
end
# Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 2a014bf976..4808663aa9 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -245,7 +245,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
namespace :account do
match 'shorthand'
- match 'description', :to => "description", :as => "description"
+ match 'description', :to => :description, :as => "description"
+ match ':action/callback', :action => /twitter|github/, :to => "callbacks", :as => :callback
resource :subscription, :credit, :credit_card
root :to => "account#index"
@@ -1159,7 +1160,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
- def test_convention_match_with_no_scope
+ def test_match_shorthand_with_no_scope
with_test_routes do
assert_equal '/account/overview', account_overview_path
get '/account/overview'
@@ -1167,7 +1168,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
- def test_convention_match_inside_namespace
+ def test_match_shorthand_inside_namespace
with_test_routes do
assert_equal '/account/shorthand', account_shorthand_path
get '/account/shorthand'
@@ -1175,6 +1176,17 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_scoped_controller_with_namespace_and_action
+ with_test_routes do
+ assert_equal '/account/twitter/callback', account_callback_path("twitter")
+ get '/account/twitter/callback'
+ assert_equal 'account/callbacks#twitter', @response.body
+
+ get '/account/whatever/callback'
+ assert_equal 'Not Found', @response.body
+ end
+ end
+
def test_convention_match_nested_and_with_leading_slash
with_test_routes do
assert_equal '/account/nested/overview', account_nested_overview_path
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 4b9e41803f..9ddfa13c16 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -200,6 +200,11 @@ class FormHelperTest < ActionView::TestCase
assert_equal object_name, "post[]"
end
+ def test_file_field_has_no_size
+ expected = '<input id="user_avatar" name="user[avatar]" type="file" />'
+ assert_dom_equal expected, file_field("user", "avatar")
+ end
+
def test_hidden_field
assert_dom_equal '<input id="post_title" name="post[title]" type="hidden" value="Hello World" />',
hidden_field("post", "title")
diff --git a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
index 7c48472799..9fcb94d48a 100644
--- a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
+++ b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
@@ -1,3 +1,4 @@
+require 'set'
require 'active_model/mass_assignment_security/sanitizer'
module ActiveModel
@@ -36,4 +37,4 @@ module ActiveModel
end
end
end
-end \ No newline at end of file
+end
diff --git a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
index 367207aab3..015153ec7c 100644
--- a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
+++ b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
@@ -31,7 +31,7 @@ class SanitizerTest < ActiveModel::TestCase
log = StringIO.new
@sanitizer.logger = Logger.new(log)
@sanitizer.sanitize(original_attributes)
- assert (log.string =~ /admin/), "Should log removed attributes: #{log.string}"
+ assert_match(/admin/, log.string, "Should log removed attributes: #{log.string}")
end
end
diff --git a/activemodel/test/cases/mass_assignment_security_test.rb b/activemodel/test/cases/mass_assignment_security_test.rb
index 0f7a38b0bc..c25b0fdf00 100644
--- a/activemodel/test/cases/mass_assignment_security_test.rb
+++ b/activemodel/test/cases/mass_assignment_security_test.rb
@@ -35,10 +35,10 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase
assert_equal Set.new([ 'credit_rating', 'administrator', 'phone_number', 'name']), LooseDescendantSecond.protected_attributes,
'Running attr_protected twice in one class should merge the protections'
- assert (TightPerson.protected_attributes - TightPerson.attributes_protected_by_default).blank?
+ assert_blank TightPerson.protected_attributes - TightPerson.attributes_protected_by_default
assert_equal Set.new([ 'name', 'address' ]), TightPerson.accessible_attributes
- assert (TightDescendant.protected_attributes - TightDescendant.attributes_protected_by_default).blank?
+ assert_blank TightDescendant.protected_attributes - TightDescendant.attributes_protected_by_default
assert_equal Set.new([ 'name', 'address', 'phone_number' ]), TightDescendant.accessible_attributes
end
@@ -49,4 +49,4 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase
assert_equal sanitized, { }
end
-end \ No newline at end of file
+end
diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association_scope.rb
index 22e1033a9d..cabb33c4a8 100644
--- a/activerecord/lib/active_record/associations/through_association_scope.rb
+++ b/activerecord/lib/active_record/associations/through_association_scope.rb
@@ -35,7 +35,7 @@ module ActiveRecord
@owner.class.base_class.name.to_s,
reflection.klass.columns_hash["#{as}_type"]) }
elsif reflection.macro == :belongs_to
- { reflection.klass.primary_key => @owner[reflection.primary_key_name] }
+ { reflection.klass.primary_key => @owner.class.quote_value(@owner[reflection.primary_key_name]) }
else
{ reflection.primary_key_name => owner_quoted_id }
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index c78060c956..400a0adbcf 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -398,7 +398,7 @@ module ActiveRecord #:nodoc:
delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped
delegate :find_each, :find_in_batches, :to => :scoped
- delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
+ delegate :select, :group, :order, :reorder, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
# Executes a custom SQL query against your database and returns all the results. The results will
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
index 23c42d670b..8e74eff0ab 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
@@ -66,15 +66,9 @@ module ActiveRecord
unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end
begin
- require 'rubygems'
- gem "activerecord-#{spec[:adapter]}-adapter"
require "active_record/connection_adapters/#{spec[:adapter]}_adapter"
rescue LoadError
- begin
- require "active_record/connection_adapters/#{spec[:adapter]}_adapter"
- rescue LoadError
- raise "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{$!})"
- end
+ raise "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{$!})"
end
adapter_method = "#{spec[:adapter]}_connection"
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 2fe2ae7136..dd623def2e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -431,15 +431,15 @@ module ActiveRecord
def result_as_array(res) #:nodoc:
# check if we have any binary column and if they need escaping
unescape_col = []
- for j in 0...res.nfields do
+ res.nfields.times do |j|
# unescape string passed BYTEA field (OID == 17)
unescape_col << ( res.ftype(j)==17 )
end
ary = []
- for i in 0...res.ntuples do
+ res.ntuples.times do |i|
ary << []
- for j in 0...res.nfields do
+ res.nfields.times do |j|
data = res.getvalue(i,j)
data = unescape_bytea(data) if unescape_col[j] and data.is_a?(String)
ary[i] << data
@@ -941,51 +941,37 @@ module ActiveRecord
# conversions that are required to be performed here instead of in PostgreSQLColumn.
def select(sql, name = nil)
fields, rows = select_raw(sql, name)
- result = []
- for row in rows
- row_hash = {}
- fields.each_with_index do |f, i|
- row_hash[f] = row[i]
- end
- result << row_hash
+ rows.map do |row|
+ Hash[*fields.zip(row).flatten]
end
- result
end
def select_raw(sql, name = nil)
res = execute(sql, name)
results = result_as_array(res)
- fields = []
- rows = []
- if res.ntuples > 0
- fields = res.fields
- results.each do |row|
- hashed_row = {}
- row.each_index do |cell_index|
- # If this is a money type column and there are any currency symbols,
- # then strip them off. Indeed it would be prettier to do this in
- # PostgreSQLColumn.string_to_decimal but would break form input
- # fields that call value_before_type_cast.
- if res.ftype(cell_index) == MONEY_COLUMN_TYPE_OID
- # Because money output is formatted according to the locale, there are two
- # cases to consider (note the decimal separators):
- # (1) $12,345,678.12
- # (2) $12.345.678,12
- case column = row[cell_index]
- when /^-?\D+[\d,]+\.\d{2}$/ # (1)
- row[cell_index] = column.gsub(/[^-\d\.]/, '')
- when /^-?\D+[\d\.]+,\d{2}$/ # (2)
- row[cell_index] = column.gsub(/[^-\d,]/, '').sub(/,/, '.')
- end
+ fields = res.fields
+ results.each do |row|
+ row.each_with_index do |cell, cell_index|
+ # If this is a money type column and there are any currency symbols,
+ # then strip them off. Indeed it would be prettier to do this in
+ # PostgreSQLColumn.string_to_decimal but would break form input
+ # fields that call value_before_type_cast.
+ if res.ftype(cell_index) == MONEY_COLUMN_TYPE_OID
+ # Because money output is formatted according to the locale, there are two
+ # cases to consider (note the decimal separators):
+ # (1) $12,345,678.12
+ # (2) $12.345.678,12
+ case cell
+ when /^-?\D+[\d,]+\.\d{2}$/ # (1)
+ cell.gsub!(/[^-\d\.]/, '')
+ when /^-?\D+[\d\.]+,\d{2}$/ # (2)
+ cell.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
end
-
- hashed_row[fields[cell_index]] = column
end
- rows << row
end
end
res.clear
- return fields, rows
+ return fields, results
end
# Returns the list of a table's column names, data types, and default values.
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 117cf447df..e812a0371b 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -190,16 +190,21 @@ module ActiveRecord
def indexes(table_name, name = nil) #:nodoc:
execute("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row|
- index = IndexDefinition.new(table_name, row['name'])
- index.unique = row['unique'].to_i != 0
- index.columns = execute("PRAGMA index_info('#{index.name}')").map { |col| col['name'] }
- index
+ IndexDefinition.new(
+ table_name,
+ row['name'],
+ row['unique'].to_i != 0,
+ execute("PRAGMA index_info('#{row['name']}')").map { |col|
+ col['name']
+ })
end
end
def primary_key(table_name) #:nodoc:
- column = table_structure(table_name).find {|field| field['pk'].to_i == 1}
- column ? column['name'] : nil
+ column = table_structure(table_name).find { |field|
+ field['pk'].to_i == 1
+ }
+ column && column['name']
end
def remove_index!(table_name, index_name) #:nodoc:
@@ -278,10 +283,8 @@ module ActiveRecord
def select(sql, name = nil) #:nodoc:
execute(sql, name).map do |row|
record = {}
- row.each_key do |key|
- if key.is_a?(String)
- record[key.sub(/^"?\w+"?\./, '')] = row[key]
- end
+ row.each do |key, value|
+ record[key.sub(/^"?\w+"?\./, '')] = value if key.is_a?(String)
end
record
end
@@ -378,9 +381,9 @@ module ActiveRecord
def default_primary_key_type
if supports_autoincrement?
- 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'.freeze
+ 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'
else
- 'INTEGER PRIMARY KEY NOT NULL'.freeze
+ 'INTEGER PRIMARY KEY NOT NULL'
end
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 828a8b41b6..e53cc5ee8c 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -105,18 +105,17 @@ module ActiveRecord
# Updates a single attribute and saves the record without going through the normal validation procedure
# or callbacks. This is especially useful for boolean flags on existing records.
def update_attribute(name, value)
- send("#{name}=", value)
- hash = { name => read_attribute(name) }
+ changes = record_update_timestamps || {}
- if record_update_timestamps
- timestamp_attributes_for_update_in_model.each do |column|
- hash[column] = read_attribute(column)
- end
+ if name
+ name = name.to_s
+ send("#{name}=", value)
+ changes[name] = read_attribute(name)
end
- @changed_attributes.delete(name.to_s)
+ @changed_attributes.except!(*changes.keys)
primary_key = self.class.primary_key
- self.class.update_all(hash, { primary_key => self[primary_key] }) == 1
+ self.class.update_all(changes, { primary_key => self[primary_key] }) == 1
end
# Updates all the attributes from the passed-in Hash and saves the record.
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 2808e199fe..eff51a7c87 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -16,11 +16,7 @@ module ActiveRecord
config.generators.orm :active_record, :migration => true,
:timestamps => true
- config.app_middleware.insert_after "::ActionDispatch::Callbacks",
- "ActiveRecord::QueryCache"
-
- config.app_middleware.insert_after "::ActionDispatch::Callbacks",
- "ActiveRecord::ConnectionAdapters::ConnectionManagement"
+ config.app_middleware.insert_after "::ActionDispatch::Callbacks", "ActiveRecord::QueryCache"
rake_tasks do
load "active_record/railties/databases.rake"
@@ -72,6 +68,13 @@ module ActiveRecord
end
end
+ initializer "active_record.add_concurrency_middleware" do |app|
+ if app.config.allow_concurrency
+ app.config.middleware.insert_after "::ActionDispatch::Callbacks",
+ "ActiveRecord::ConnectionAdapters::ConnectionManagement"
+ end
+ end
+
config.after_initialize do
ActiveSupport.on_load(:active_record) do
instantiate_observers
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index d9fc1b4940..7499100f55 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -10,7 +10,7 @@ module ActiveRecord
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches
- delegate :to_xml, :to_json, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
+ delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
delegate :insert, :to => :arel
attr_reader :table, :klass
@@ -74,6 +74,8 @@ module ActiveRecord
@records
end
+ def as_json(options = nil) to_a end #:nodoc:
+
# Returns size of the records.
def size
loaded? ? @records.length : count
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 1075a60f07..341cc87be5 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -21,27 +21,20 @@ module ActiveRecord
end
# Saves the record with the updated_at/on attributes set to the current time.
- # If the save fails because of validation errors, an
- # ActiveRecord::RecordInvalid exception is raised. If an attribute name is passed,
- # that attribute is used for the touch instead of the updated_at/on attributes.
+ # Please note that no validation is performed and no callbacks are executed.
+ # If an attribute name is passed, that attribute is updated along with
+ # updated_at/on attributes.
#
# Examples:
#
- # product.touch # updates updated_at
- # product.touch(:designed_at) # updates the designed_at attribute
+ # product.touch # updates updated_at/on
+ # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
def touch(attribute = nil)
- current_time = current_time_from_proper_timezone
-
- if attribute
- write_attribute(attribute, current_time)
- else
- timestamp_attributes_for_update_in_model.each { |column| write_attribute(column.to_s, current_time) }
- end
-
- save!
+ update_attribute(attribute, current_time_from_proper_timezone)
end
private
+
def create #:nodoc:
if record_timestamps
current_time = current_time_from_proper_timezone
@@ -58,17 +51,16 @@ module ActiveRecord
end
def update(*args) #:nodoc:
- record_update_timestamps
+ record_update_timestamps if !partial_updates? || changed?
super
end
- def record_update_timestamps
- if record_timestamps && (!partial_updates? || changed?)
- current_time = current_time_from_proper_timezone
- timestamp_attributes_for_update_in_model.each { |column| write_attribute(column.to_s, current_time) }
- true
- else
- false
+ def record_update_timestamps #:nodoc:
+ return unless record_timestamps
+ current_time = current_time_from_proper_timezone
+ timestamp_attributes_for_update_in_model.inject({}) do |hash, column|
+ hash[column.to_s] = write_attribute(column.to_s, current_time)
+ hash
end
end
diff --git a/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb b/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb
index 69cfb00faf..ce0b2f5f5b 100644
--- a/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb
@@ -83,17 +83,127 @@ module ActiveRecord
assert_equal 0, @ctx.select_rows(count_sql).first.first
end
+ def test_tables
+ assert_equal %w{ items }, @ctx.tables
+
+ @ctx.execute <<-eosql
+ CREATE TABLE people (
+ id integer PRIMARY KEY AUTOINCREMENT,
+ number integer
+ )
+ eosql
+ assert_equal %w{ items people }.sort, @ctx.tables.sort
+ end
+
+ def test_tables_logs_name
+ name = "hello"
+ assert_logged [[name]] do
+ @ctx.tables(name)
+ assert_not_nil @ctx.logged.first.shift
+ end
+ end
+
+ def test_columns
+ columns = @ctx.columns('items').sort_by { |x| x.name }
+ assert_equal 2, columns.length
+ assert_equal %w{ id number }.sort, columns.map { |x| x.name }
+ assert_equal [nil, nil], columns.map { |x| x.default }
+ assert_equal [true, true], columns.map { |x| x.null }
+ end
+
+ def test_columns_with_default
+ @ctx.execute <<-eosql
+ CREATE TABLE columns_with_default (
+ id integer PRIMARY KEY AUTOINCREMENT,
+ number integer default 10
+ )
+ eosql
+ column = @ctx.columns('columns_with_default').find { |x|
+ x.name == 'number'
+ }
+ assert_equal 10, column.default
+ end
+
+ def test_columns_with_not_null
+ @ctx.execute <<-eosql
+ CREATE TABLE columns_with_default (
+ id integer PRIMARY KEY AUTOINCREMENT,
+ number integer not null
+ )
+ eosql
+ column = @ctx.columns('columns_with_default').find { |x|
+ x.name == 'number'
+ }
+ assert !column.null, "column should not be null"
+ end
+
+ def test_indexes_logs
+ intercept_logs_on @ctx
+ assert_difference('@ctx.logged.length') do
+ @ctx.indexes('items')
+ end
+ assert_match(/items/, @ctx.logged.last.first)
+ end
+
+ def test_no_indexes
+ assert_equal [], @ctx.indexes('items')
+ end
+
+ def test_index
+ @ctx.add_index 'items', 'id', :unique => true, :name => 'fun'
+ index = @ctx.indexes('items').find { |idx| idx.name == 'fun' }
+
+ assert_equal 'items', index.table
+ assert index.unique, 'index is unique'
+ assert_equal ['id'], index.columns
+ end
+
+ def test_non_unique_index
+ @ctx.add_index 'items', 'id', :name => 'fun'
+ index = @ctx.indexes('items').find { |idx| idx.name == 'fun' }
+ assert !index.unique, 'index is not unique'
+ end
+
+ def test_compound_index
+ @ctx.add_index 'items', %w{ id number }, :name => 'fun'
+ index = @ctx.indexes('items').find { |idx| idx.name == 'fun' }
+ assert_equal %w{ id number }.sort, index.columns.sort
+ end
+
+ def test_primary_key
+ assert_equal 'id', @ctx.primary_key('items')
+
+ @ctx.execute <<-eosql
+ CREATE TABLE foos (
+ internet integer PRIMARY KEY AUTOINCREMENT,
+ number integer not null
+ )
+ eosql
+ assert_equal 'internet', @ctx.primary_key('foos')
+ end
+
+ def test_no_primary_key
+ @ctx.execute 'CREATE TABLE failboat (number integer not null)'
+ assert_nil @ctx.primary_key('failboat')
+ end
+
+ private
+
def assert_logged logs
+ intercept_logs_on @ctx
+ yield
+ assert_equal logs, @ctx.logged
+ end
+
+ def intercept_logs_on ctx
@ctx.extend(Module.new {
- attr_reader :logged
+ attr_accessor :logged
def log sql, name
- @logged ||= []
@logged << [sql, name]
yield
end
})
- yield
- assert_equal logs, @ctx.logged
+ @ctx.logged = []
end
end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index a52cedd8c2..f1440804d2 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -549,7 +549,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert the_client.new_record?
end
- def test_find_or_create
+ 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
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index 178c57435b..3fcd150422 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -6,9 +6,12 @@ require 'models/membership'
require 'models/sponsor'
require 'models/organization'
require 'models/member_detail'
+require 'models/minivan'
+require 'models/dashboard'
+require 'models/speedometer'
class HasOneThroughAssociationsTest < ActiveRecord::TestCase
- fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations
+ fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations, :minivans, :dashboards, :speedometers
def setup
@member = members(:groucho)
@@ -202,4 +205,11 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
Club.find(@club.id, :include => :sponsored_member).save!
end
end
+
+ def test_value_is_properly_quoted
+ minivan = Minivan.find('m1')
+ assert_nothing_raised do
+ minivan.dashboard
+ end
+ end
end
diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb
index c275557da8..2bc746c0b8 100644
--- a/activerecord/test/cases/json_serialization_test.rb
+++ b/activerecord/test/cases/json_serialization_test.rb
@@ -201,4 +201,11 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase
}
assert_equal %({"1":{"author":{"name":"David"}}}), ActiveSupport::JSON.encode(authors_hash, :only => [1, :name])
end
+
+ def test_should_be_able_to_encode_relation
+ authors_relation = Author.where(:id => [@david.id, @mary.id])
+
+ json = ActiveSupport::JSON.encode authors_relation, :only => :name
+ assert_equal '[{"author":{"name":"David"}},{"author":{"name":"Mary"}}]', json
+ end
end
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index 41dcdbcd37..a5a3b3ef38 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -364,6 +364,12 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected, received
end
+ def test_named_scope_reorders_default
+ expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.name }
+ received = DeveloperOrderedBySalary.reordered_by_name.find(:all).collect { |dev| dev.name }
+ assert_equal expected, received
+ end
+
def test_nested_exclusive_scope
expected = Developer.find(:all, :limit => 100).collect { |dev| dev.salary }
received = DeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 549c4af6b1..f765540808 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -25,16 +25,26 @@ class TimestampTest < ActiveRecord::TestCase
end
def test_touching_a_record_updates_its_timestamp
+ previous_salary = @developer.salary
+ @developer.salary = previous_salary + 10000
@developer.touch
assert_not_equal @previously_updated_at, @developer.updated_at
+ assert_equal previous_salary + 10000, @developer.salary
+ assert @developer.salary_changed?, 'developer salary should have changed'
+ assert @developer.changed?, 'developer should be marked as changed'
+ @developer.reload
+ assert_equal previous_salary, @developer.salary
end
def test_touching_a_different_attribute
previously_created_at = @developer.created_at
@developer.touch(:created_at)
+ assert !@developer.created_at_changed? , 'created_at should not be changed'
+ assert !@developer.changed?, 'record should not be changed'
assert_not_equal previously_created_at, @developer.created_at
+ assert_not_equal @previously_updated_at, @developer.updated_at
end
def test_saving_a_record_with_a_belongs_to_that_specifies_touching_the_parent_should_update_the_parent_updated_at
diff --git a/activerecord/test/fixtures/dashboards.yml b/activerecord/test/fixtures/dashboards.yml
new file mode 100644
index 0000000000..e75bf46e6c
--- /dev/null
+++ b/activerecord/test/fixtures/dashboards.yml
@@ -0,0 +1,3 @@
+cool_first:
+ dashboard_id: d1
+ name: my_dashboard \ No newline at end of file
diff --git a/activerecord/test/fixtures/minivans.yml b/activerecord/test/fixtures/minivans.yml
new file mode 100644
index 0000000000..e7a2ab77eb
--- /dev/null
+++ b/activerecord/test/fixtures/minivans.yml
@@ -0,0 +1,4 @@
+cool_first:
+ minivan_id: m1
+ name: my_minivan
+ speedometer_id: s1
diff --git a/activerecord/test/fixtures/speedometers.yml b/activerecord/test/fixtures/speedometers.yml
new file mode 100644
index 0000000000..6a471aba0a
--- /dev/null
+++ b/activerecord/test/fixtures/speedometers.yml
@@ -0,0 +1,4 @@
+cool_first:
+ speedometer_id: s1
+ name: my_speedometer
+ dashboard_id: d1 \ No newline at end of file
diff --git a/activerecord/test/models/dashboard.rb b/activerecord/test/models/dashboard.rb
new file mode 100644
index 0000000000..a8a25834b1
--- /dev/null
+++ b/activerecord/test/models/dashboard.rb
@@ -0,0 +1,3 @@
+class Dashboard < ActiveRecord::Base
+ set_primary_key :dashboard_id
+end \ No newline at end of file
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index de68fd7f24..c61c583c1d 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -88,6 +88,7 @@ class DeveloperOrderedBySalary < ActiveRecord::Base
self.table_name = 'developers'
default_scope :order => 'salary DESC'
scope :by_name, :order => 'name DESC'
+ scope :reordered_by_name, reorder('name DESC')
def self.all_ordered_by_name
with_scope(:find => { :order => 'name DESC' }) do
diff --git a/activerecord/test/models/minivan.rb b/activerecord/test/models/minivan.rb
new file mode 100644
index 0000000000..c753319a20
--- /dev/null
+++ b/activerecord/test/models/minivan.rb
@@ -0,0 +1,6 @@
+class Minivan < ActiveRecord::Base
+ set_primary_key :minivan_id
+
+ belongs_to :speedometer
+ has_one :dashboard, :through => :speedometer
+end \ No newline at end of file
diff --git a/activerecord/test/models/speedometer.rb b/activerecord/test/models/speedometer.rb
new file mode 100644
index 0000000000..94743eff8e
--- /dev/null
+++ b/activerecord/test/models/speedometer.rb
@@ -0,0 +1,4 @@
+class Speedometer < ActiveRecord::Base
+ set_primary_key :speedometer_id
+ belongs_to :dashboard
+end \ No newline at end of file
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index bea351b95a..641726b43f 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -164,6 +164,11 @@ ActiveRecord::Schema.define do
t.string :address_country
t.string :gps_location
end
+
+ create_table :dashboards, :force => true, :id => false do |t|
+ t.string :dashboard_id
+ t.string :name
+ end
create_table :developers, :force => true do |t|
t.string :name
@@ -290,6 +295,12 @@ ActiveRecord::Schema.define do
t.boolean :favourite
t.integer :lock_version, :default => 0
end
+
+ create_table :minivans, :force => true, :id => false do |t|
+ t.string :minivan_id
+ t.string :name
+ t.string :speedometer_id
+ end
create_table :minimalistics, :force => true do |t|
end
@@ -452,6 +463,12 @@ ActiveRecord::Schema.define do
t.string :name
t.integer :ship_id
end
+
+ create_table :speedometers, :force => true, :id => false do |t|
+ t.string :speedometer_id
+ t.string :name
+ t.string :dashboard_id
+ end
create_table :sponsors, :force => true do |t|
t.integer :club_id
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb
index e11de5f67a..3e16e01d89 100644
--- a/activesupport/test/notifications_test.rb
+++ b/activesupport/test/notifications_test.rb
@@ -198,9 +198,9 @@ module Notifications
time = Time.now
event = event(:foo, time, time + 0.01, random_id, {})
- assert_equal :foo, event.name
- assert_equal time, event.time
- assert_equal 10.0, event.duration
+ assert_equal :foo, event.name
+ assert_equal time, event.time
+ assert_in_delta 10.0, event.duration, 0.00000000000001
end
def test_events_consumes_information_given_as_payload
diff --git a/ci/ci_build.rb b/ci/ci_build.rb
index ee6c357519..17f4ba5d51 100755
--- a/ci/ci_build.rb
+++ b/ci/ci_build.rb
@@ -19,9 +19,9 @@ puts "[CruiseControl] Rails build"
build_results = {}
# Install required version of bundler.
-bundler_install_cmd = "gem install bundler --no-ri --no-rdoc"
-puts "Running command: #{bundler_install_cmd}"
-build_results[:install_bundler] = system bundler_install_cmd
+#bundler_install_cmd = "gem install bundler --no-ri --no-rdoc"
+#puts "Running command: #{bundler_install_cmd}"
+#build_results[:install_bundler] = system bundler_install_cmd
cd root_dir do
puts
diff --git a/rails.gemspec b/rails.gemspec
index 819513c4b5..2d10ce78a0 100644
--- a/rails.gemspec
+++ b/rails.gemspec
@@ -25,5 +25,5 @@ Gem::Specification.new do |s|
s.add_dependency('activeresource', version)
s.add_dependency('actionmailer', version)
s.add_dependency('railties', version)
- s.add_dependency('bundler', '>= 1.0.0.beta.2')
+ s.add_dependency('bundler', '>= 1.0.0.beta.3')
end
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index 4ff10091b1..d8e916f45f 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -98,7 +98,17 @@ module ApplicationTests
require "#{app_path}/config/environment"
- expects = [ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActiveRecord::SessionStore]
+ expects = [ActiveRecord::QueryCache, ActiveRecord::SessionStore]
+ middleware = Rails.application.config.middleware.map { |m| m.klass }
+ assert_equal expects, middleware & expects
+ end
+
+ test "database middleware initializes when allow concurrency is true" do
+ add_to_config "config.threadsafe!"
+
+ require "#{app_path}/config/environment"
+
+ expects = [ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache]
middleware = Rails.application.config.middleware.map { |m| m.klass }
assert_equal expects, middleware & expects
end
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index e66e81ea2c..ed06b4c767 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -28,7 +28,6 @@ module ApplicationTests
"ActionDispatch::RemoteIp",
"Rack::Sendfile",
"ActionDispatch::Callbacks",
- "ActiveRecord::ConnectionAdapters::ConnectionManagement",
"ActiveRecord::QueryCache",
"ActionDispatch::Cookies",
"ActionDispatch::Session::CookieStore",