diff options
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", |