activerecord 1.11.1 → 1.12.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (102) hide show
  1. data/CHANGELOG +198 -0
  2. data/lib/active_record.rb +19 -14
  3. data/lib/active_record/acts/list.rb +8 -6
  4. data/lib/active_record/acts/tree.rb +33 -10
  5. data/lib/active_record/aggregations.rb +1 -7
  6. data/lib/active_record/associations.rb +151 -82
  7. data/lib/active_record/associations/association_collection.rb +25 -0
  8. data/lib/active_record/associations/association_proxy.rb +9 -8
  9. data/lib/active_record/associations/belongs_to_association.rb +19 -5
  10. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +44 -69
  11. data/lib/active_record/associations/has_many_association.rb +6 -14
  12. data/lib/active_record/associations/has_one_association.rb +5 -3
  13. data/lib/active_record/base.rb +344 -130
  14. data/lib/active_record/callbacks.rb +2 -2
  15. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +128 -0
  16. data/lib/active_record/connection_adapters/abstract/database_statements.rb +104 -0
  17. data/lib/active_record/connection_adapters/abstract/quoting.rb +51 -0
  18. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +249 -0
  19. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +245 -0
  20. data/lib/active_record/connection_adapters/abstract_adapter.rb +29 -464
  21. data/lib/active_record/connection_adapters/db2_adapter.rb +40 -10
  22. data/lib/active_record/connection_adapters/mysql_adapter.rb +131 -60
  23. data/lib/active_record/connection_adapters/oci_adapter.rb +106 -26
  24. data/lib/active_record/connection_adapters/postgresql_adapter.rb +211 -62
  25. data/lib/active_record/connection_adapters/sqlite_adapter.rb +193 -44
  26. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +24 -15
  27. data/lib/active_record/fixtures.rb +47 -24
  28. data/lib/active_record/migration.rb +34 -5
  29. data/lib/active_record/observer.rb +32 -2
  30. data/lib/active_record/query_cache.rb +12 -11
  31. data/lib/active_record/schema.rb +58 -0
  32. data/lib/active_record/schema_dumper.rb +84 -0
  33. data/lib/active_record/transactions.rb +1 -3
  34. data/lib/active_record/validations.rb +40 -26
  35. data/lib/active_record/vendor/mysql.rb +6 -0
  36. data/lib/active_record/version.rb +9 -0
  37. data/rakefile +5 -16
  38. data/test/abstract_unit.rb +6 -11
  39. data/test/adapter_test.rb +58 -0
  40. data/test/ar_schema_test.rb +33 -0
  41. data/test/association_callbacks_test.rb +14 -0
  42. data/test/associations_go_eager_test.rb +56 -14
  43. data/test/associations_test.rb +245 -25
  44. data/test/base_test.rb +205 -34
  45. data/test/binary_test.rb +25 -42
  46. data/test/callbacks_test.rb +75 -0
  47. data/test/conditions_scoping_test.rb +136 -0
  48. data/test/connections/native_mysql/connection.rb +0 -4
  49. data/test/connections/native_sqlite3/in_memory_connection.rb +17 -0
  50. data/test/copy_table_sqlite.rb +64 -0
  51. data/test/deprecated_associations_test.rb +7 -6
  52. data/test/deprecated_finder_test.rb +3 -3
  53. data/test/finder_test.rb +33 -3
  54. data/test/fixtures/accounts.yml +5 -0
  55. data/test/fixtures/categories_ordered.yml +7 -0
  56. data/test/fixtures/category.rb +11 -1
  57. data/test/fixtures/comment.rb +22 -2
  58. data/test/fixtures/comments.yml +6 -0
  59. data/test/fixtures/companies.yml +15 -0
  60. data/test/fixtures/company.rb +24 -1
  61. data/test/fixtures/db_definitions/db2.drop.sql +5 -1
  62. data/test/fixtures/db_definitions/db2.sql +15 -1
  63. data/test/fixtures/db_definitions/mysql.drop.sql +2 -0
  64. data/test/fixtures/db_definitions/mysql.sql +17 -2
  65. data/test/fixtures/db_definitions/oci.drop.sql +37 -5
  66. data/test/fixtures/db_definitions/oci.sql +47 -4
  67. data/test/fixtures/db_definitions/oci2.drop.sql +1 -1
  68. data/test/fixtures/db_definitions/oci2.sql +2 -2
  69. data/test/fixtures/db_definitions/postgresql.drop.sql +4 -0
  70. data/test/fixtures/db_definitions/postgresql.sql +33 -4
  71. data/test/fixtures/db_definitions/sqlite.drop.sql +2 -0
  72. data/test/fixtures/db_definitions/sqlite.sql +16 -2
  73. data/test/fixtures/db_definitions/sqlserver.drop.sql +2 -0
  74. data/test/fixtures/db_definitions/sqlserver.sql +16 -2
  75. data/test/fixtures/developer.rb +1 -1
  76. data/test/fixtures/flowers.jpg +0 -0
  77. data/test/fixtures/keyboard.rb +3 -0
  78. data/test/fixtures/mixins.yml +11 -1
  79. data/test/fixtures/order.rb +4 -0
  80. data/test/fixtures/post.rb +4 -0
  81. data/test/fixtures/posts.yml +7 -0
  82. data/test/fixtures/project.rb +1 -0
  83. data/test/fixtures/subject.rb +4 -0
  84. data/test/fixtures/subscriber.rb +2 -4
  85. data/test/fixtures/topics.yml +2 -2
  86. data/test/fixtures_test.rb +79 -7
  87. data/test/inheritance_test.rb +2 -2
  88. data/test/lifecycle_test.rb +14 -6
  89. data/test/migration_test.rb +164 -6
  90. data/test/mixin_test.rb +78 -2
  91. data/test/pk_test.rb +25 -1
  92. data/test/readonly_test.rb +31 -0
  93. data/test/reflection_test.rb +4 -1
  94. data/test/schema_dumper_test.rb +19 -0
  95. data/test/schema_test_postgresql.rb +3 -2
  96. data/test/synonym_test_oci.rb +17 -0
  97. data/test/threaded_connections_test.rb +2 -1
  98. data/test/transactions_test.rb +109 -10
  99. data/test/validations_test.rb +70 -42
  100. metadata +25 -5
  101. data/test/fixtures/associations.png +0 -0
  102. data/test/thread_safety_test.rb +0 -36
@@ -0,0 +1,84 @@
1
+ module ActiveRecord
2
+ # This class is used to dump the database schema for some connection to some
3
+ # output format (i.e., ActiveRecord::Schema).
4
+ class SchemaDumper #:nodoc:
5
+ private_class_method :new
6
+
7
+ def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT)
8
+ new(connection).dump(stream)
9
+ stream
10
+ end
11
+
12
+ def dump(stream)
13
+ header(stream)
14
+ tables(stream)
15
+ trailer(stream)
16
+ stream
17
+ end
18
+
19
+ private
20
+
21
+ def initialize(connection)
22
+ @connection = connection
23
+ @types = @connection.native_database_types
24
+ @info = @connection.select_one("SELECT * FROM schema_info") rescue nil
25
+ end
26
+
27
+ def header(stream)
28
+ define_params = @info ? ":version => #{@info['version']}" : ""
29
+
30
+ stream.puts <<HEADER
31
+ # This file is autogenerated. Instead of editing this file, please use the
32
+ # migrations feature of ActiveRecord to incrementally modify your database, and
33
+ # then regenerate this schema definition.
34
+
35
+ ActiveRecord::Schema.define(#{define_params}) do
36
+
37
+ HEADER
38
+ end
39
+
40
+ def trailer(stream)
41
+ stream.puts "end"
42
+ end
43
+
44
+ def tables(stream)
45
+ @connection.tables.sort.each do |tbl|
46
+ next if tbl == "schema_info"
47
+ table(tbl, stream)
48
+ end
49
+ end
50
+
51
+ def table(table, stream)
52
+ columns = @connection.columns(table)
53
+
54
+ stream.print " create_table #{table.inspect}"
55
+ stream.print ", :id => false" if !columns.detect { |c| c.name == "id" }
56
+ stream.print ", :force => true"
57
+ stream.puts " do |t|"
58
+
59
+ columns.each do |column|
60
+ next if column.name == "id"
61
+ stream.print " t.column #{column.name.inspect}, #{column.type.inspect}"
62
+ stream.print ", :limit => #{column.limit.inspect}" if column.limit != @types[column.type][:limit]
63
+ stream.print ", :default => #{column.default.inspect}" if !column.default.nil?
64
+ stream.print ", :null => false" if !column.null
65
+ stream.puts
66
+ end
67
+
68
+ stream.puts " end"
69
+ stream.puts
70
+
71
+ indexes(table, stream)
72
+ end
73
+
74
+ def indexes(table, stream)
75
+ indexes = @connection.indexes(table)
76
+ indexes.each do |index|
77
+ stream.print " add_index #{index.table.inspect}, #{index.columns.inspect}, :name => #{index.name.inspect}"
78
+ stream.print ", :unique => true" if index.unique
79
+ stream.puts
80
+ end
81
+ stream.puts unless indexes.empty?
82
+ end
83
+ end
84
+ end
@@ -81,9 +81,7 @@ module ActiveRecord
81
81
  # Tribute: Object-level transactions are implemented by Transaction::Simple by Austin Ziegler.
82
82
  module ClassMethods
83
83
  def transaction(*objects, &block)
84
- previous_handler = trap('TERM') do
85
- raise TransactionError, "Transaction aborted"
86
- end
84
+ previous_handler = trap('TERM') { raise TransactionError, "Transaction aborted" }
87
85
  lock_mutex
88
86
 
89
87
  begin
@@ -21,8 +21,8 @@ module ActiveRecord
21
21
  :too_short => "is too short (min is %d characters)",
22
22
  :wrong_length => "is the wrong length (should be %d characters)",
23
23
  :taken => "has already been taken",
24
- :not_a_number => "is not a number",
25
- }
24
+ :not_a_number => "is not a number"
25
+ }
26
26
 
27
27
  # Holds a hash with all the default error messages, such that they can be replaced by your own copy or localizations.
28
28
  cattr_accessor :default_error_messages
@@ -179,7 +179,8 @@ module ActiveRecord
179
179
  # person.count # => 2
180
180
  # person.errors.on "last_name" # => "can't be empty"
181
181
  # person.errors.on "phone_number" # => "has invalid format"
182
- # person.each_full { |msg| puts msg } # => "Last name can't be empty\n" +
182
+ # person.errors.each_full { |msg| puts msg }
183
+ # # => "Last name can't be empty\n" +
183
184
  # "Phone number has invalid format"
184
185
  #
185
186
  # person.attributes = { "last_name" => "Heinemeier", "phone_number" => "555-555" }
@@ -398,9 +399,11 @@ module ActiveRecord
398
399
  # method, proc or string should return or evaluate to a true or false value.
399
400
  def validates_length_of(*attrs)
400
401
  # Merge given options with defaults.
401
- options = {:too_long => ActiveRecord::Errors.default_error_messages[:too_long],
402
- :too_short => ActiveRecord::Errors.default_error_messages[:too_short],
403
- :wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length]}.merge(DEFAULT_VALIDATION_OPTIONS)
402
+ options = {
403
+ :too_long => ActiveRecord::Errors.default_error_messages[:too_long],
404
+ :too_short => ActiveRecord::Errors.default_error_messages[:too_short],
405
+ :wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length]
406
+ }.merge(DEFAULT_VALIDATION_OPTIONS)
404
407
  options.update(attrs.pop.symbolize_keys) if attrs.last.is_a?(Hash)
405
408
 
406
409
  # Ensure that one and only one range option is specified.
@@ -418,24 +421,31 @@ module ActiveRecord
418
421
  option = range_options.first
419
422
  option_value = options[range_options.first]
420
423
 
421
- # Declare different validations per option.
422
- validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" }
423
- message_options = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }
424
-
425
424
  case option
426
425
  when :within, :in
427
- raise ArgumentError, ':within must be a Range' unless option_value.is_a?(Range) # '
428
- (options_without_range = options.dup).delete(option)
429
- (options_with_minimum = options_without_range.dup).store(:minimum, option_value.begin)
430
- validates_length_of attrs, options_with_minimum
431
- (options_with_maximum = options_without_range.dup).store(:maximum, option_value.end)
432
- validates_length_of attrs, options_with_maximum
426
+ raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
427
+
428
+ too_short = options[:too_short] % option_value.begin
429
+ too_long = options[:too_long] % option_value.end
430
+
431
+ validates_each(attrs, options) do |record, attr, value|
432
+ if value.nil? or value.size < option_value.begin
433
+ record.errors.add(attr, too_short)
434
+ elsif value.size > option_value.end
435
+ record.errors.add(attr, too_long)
436
+ end
437
+ end
433
438
  when :is, :minimum, :maximum
434
- raise ArgumentError, ":#{option} must be a nonnegative Integer" unless option_value.is_a?(Integer) and option_value >= 0 # '
435
- message = options[:message] || options[message_options[option]]
436
- message = (message % option_value) rescue message
439
+ raise ArgumentError, ":#{option} must be a nonnegative Integer" unless option_value.is_a?(Integer) and option_value >= 0
440
+
441
+ # Declare different validations per option.
442
+ validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" }
443
+ message_options = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }
444
+
445
+ message = (options[:message] || options[message_options[option]]) % option_value
446
+
437
447
  validates_each(attrs, options) do |record, attr, value|
438
- record.errors.add(attr, message) if value.nil? or !value.size.method(validity_checks[option])[option_value]
448
+ record.errors.add(attr, message) unless !value.nil? and value.size.method(validity_checks[option])[option_value]
439
449
  end
440
450
  end
441
451
  end
@@ -588,7 +598,7 @@ module ActiveRecord
588
598
 
589
599
  validates_each(attr_names, configuration) do |record, attr_name, value|
590
600
  record.errors.add(attr_name, configuration[:message]) unless
591
- (value.is_a?(Array) ? value : [value]).inject(true) { |memo, r| (r.nil? or r.valid?) and memo }
601
+ (value.is_a?(Array) ? value : [value]).all? { |r| r.nil? or r.valid? }
592
602
  end
593
603
  end
594
604
 
@@ -604,7 +614,7 @@ module ActiveRecord
604
614
  # * <tt>message</tt> - A custom error message (default is: "is not a number")
605
615
  # * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
606
616
  # * <tt>only_integer</tt> Specifies whether the value has to be an integer, e.g. an integral value (default is false)
607
- # * <tt>allow_nil</tt> Skip validation if attribute is nil (default is false). Notice that for fixnum and float columsn empty strings are converted to nil
617
+ # * <tt>allow_nil</tt> Skip validation if attribute is nil (default is false). Notice that for fixnum and float columns empty strings are converted to nil
608
618
  # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
609
619
  # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
610
620
  # method, proc or string should return or evaluate to a true or false value.
@@ -647,7 +657,12 @@ module ActiveRecord
647
657
  # The validation process on save can be skipped by passing false. The regular Base#save method is
648
658
  # replaced with this when the validations module is mixed in, which it is by default.
649
659
  def save_with_validation(perform_validation = true)
650
- if perform_validation && valid? || !perform_validation then save_without_validation else false end
660
+ if perform_validation && valid? || !perform_validation
661
+ save_without_validation
662
+ true
663
+ else
664
+ false
665
+ end
651
666
  end
652
667
 
653
668
  # Attempts to save the record just like Base.save but will raise a RecordInvalid exception instead of returning false
@@ -660,7 +675,7 @@ module ActiveRecord
660
675
  # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method
661
676
  # in Base is replaced with this when the validations module is mixed in, which it is by default.
662
677
  def update_attribute_with_validation_skipping(name, value)
663
- self[name] = value
678
+ send(name.to_s + '=', value)
664
679
  save(false)
665
680
  end
666
681
 
@@ -684,8 +699,7 @@ module ActiveRecord
684
699
 
685
700
  # Returns the Errors object that holds all information about attribute error messages.
686
701
  def errors
687
- @errors = Errors.new(self) if @errors.nil?
688
- @errors
702
+ @errors ||= Errors.new(self)
689
703
  end
690
704
 
691
705
  protected
@@ -1022,6 +1022,9 @@ class Mysql
1022
1022
  end
1023
1023
  @sock.sync = true
1024
1024
  buf.join
1025
+ rescue
1026
+ errno = Error::CR_SERVER_LOST
1027
+ raise Error::new(errno, Error::err(errno))
1025
1028
  end
1026
1029
 
1027
1030
  def write(data)
@@ -1039,6 +1042,9 @@ class Mysql
1039
1042
  @pkt_nr = @pkt_nr + 1 & 0xff
1040
1043
  @sock.sync = true
1041
1044
  @sock.flush
1045
+ rescue
1046
+ errno = Error::CR_SERVER_LOST
1047
+ raise Error::new(errno, Error::err(errno))
1042
1048
  end
1043
1049
 
1044
1050
  def close()
@@ -0,0 +1,9 @@
1
+ module ActiveRecord
2
+ module Version #:nodoc:
3
+ MAJOR = 1
4
+ MINOR = 12
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
data/rakefile CHANGED
@@ -5,10 +5,11 @@ require 'rake/rdoctask'
5
5
  require 'rake/packagetask'
6
6
  require 'rake/gempackagetask'
7
7
  require 'rake/contrib/rubyforgepublisher'
8
+ require File.join(File.dirname(__FILE__), 'lib', 'active_record', 'version')
8
9
 
9
10
  PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
10
11
  PKG_NAME = 'activerecord'
11
- PKG_VERSION = '1.11.1' + PKG_BUILD
12
+ PKG_VERSION = ActiveRecord::Version::STRING + PKG_BUILD
12
13
  PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
13
14
 
14
15
  RELEASE_NAME = "REL #{PKG_VERSION}"
@@ -22,23 +23,11 @@ PKG_FILES = FileList[
22
23
 
23
24
 
24
25
  desc "Default Task"
25
- task :default => [ :test_ruby_mysql, :test_mysql_ruby, :test_sqlite, :test_sqlite3, :test_postgresql ]
26
+ task :default => [ :test_mysql, :test_sqlite, :test_postgresql ]
26
27
 
27
28
  # Run the unit tests
28
29
 
29
- Rake::TestTask.new("test_ruby_mysql") { |t|
30
- t.libs << "test" << "test/connections/native_mysql"
31
- t.pattern = 'test/*_test{,_mysql}.rb'
32
- t.verbose = true
33
- }
34
-
35
- Rake::TestTask.new("test_mysql_ruby") { |t|
36
- t.libs << "test" << "test/connections/native_mysql"
37
- t.pattern = 'test/*_test{,_mysql}.rb'
38
- t.verbose = true
39
- }
40
-
41
- for adapter in %w( postgresql sqlite sqlite3 sqlserver sqlserver_odbc db2 oci )
30
+ for adapter in %w( mysql postgresql sqlite sqlite3 sqlserver sqlserver_odbc db2 oci )
42
31
  Rake::TestTask.new("test_#{adapter}") { |t|
43
32
  t.libs << "test" << "test/connections/native_#{adapter}"
44
33
  t.pattern = "test/*_test{,_#{adapter}}.rb"
@@ -82,7 +71,7 @@ spec = Gem::Specification.new do |s|
82
71
  s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
83
72
  end
84
73
 
85
- s.add_dependency('activesupport', '= 1.1.1' + PKG_BUILD)
74
+ s.add_dependency('activesupport', '= 1.2.1' + PKG_BUILD)
86
75
 
87
76
  s.files.delete "test/fixtures/fixture_database.sqlite"
88
77
  s.files.delete "test/fixtures/fixture_database_2.sqlite"
@@ -9,16 +9,11 @@ require 'active_support/breakpoint'
9
9
  require 'connection'
10
10
 
11
11
  class Test::Unit::TestCase #:nodoc:
12
- def create_fixtures(*table_names)
13
- if block_given?
14
- Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures/", table_names) { yield }
15
- else
16
- Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures/", table_names)
17
- end
12
+ self.fixture_path = File.dirname(__FILE__) + "/fixtures/"
13
+ self.use_instantiated_fixtures = false
14
+ self.use_transactional_fixtures = (ENV['AR_NO_TX_FIXTURES'] != "yes")
15
+
16
+ def create_fixtures(*table_names, &block)
17
+ Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures/", table_names, &block)
18
18
  end
19
19
  end
20
-
21
- Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/"
22
- Test::Unit::TestCase.use_instantiated_fixtures = false
23
- Test::Unit::TestCase.use_transactional_fixtures = (ENV['AR_TX_FIXTURES'] == "yes")
24
-
@@ -0,0 +1,58 @@
1
+ require 'abstract_unit'
2
+
3
+ class AdapterTest < Test::Unit::TestCase
4
+ def setup
5
+ @connection = ActiveRecord::Base.connection
6
+ end
7
+
8
+ def test_tables
9
+ if @connection.respond_to?(:tables)
10
+ tables = @connection.tables
11
+ assert tables.include?("accounts")
12
+ assert tables.include?("authors")
13
+ assert tables.include?("tasks")
14
+ assert tables.include?("topics")
15
+ else
16
+ warn "#{@connection.class} does not respond to #tables"
17
+ end
18
+ end
19
+
20
+ def test_indexes
21
+ if @connection.respond_to?(:indexes)
22
+ indexes = @connection.indexes("accounts")
23
+ assert indexes.empty?
24
+
25
+ @connection.add_index :accounts, :firm_id
26
+ indexes = @connection.indexes("accounts")
27
+ assert_equal "accounts", indexes.first.table
28
+ assert_equal "accounts_firm_id_index", indexes.first.name
29
+ assert !indexes.first.unique
30
+ assert_equal ["firm_id"], indexes.first.columns
31
+ else
32
+ warn "#{@connection.class} does not respond to #indexes"
33
+ end
34
+
35
+ ensure
36
+ @connection.remove_index :accounts, :firm_id rescue nil
37
+ end
38
+
39
+ # test resetting sequences in odd tables in postgreSQL
40
+ if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!)
41
+ require 'fixtures/movie'
42
+ require 'fixtures/subscriber'
43
+ def test_reset_empty_table_with_custom_pk
44
+ Movie.delete_all
45
+ Movie.connection.reset_pk_sequence! 'movies'
46
+ assert_equal 1, Movie.create(:name => 'fight club').id
47
+ end
48
+
49
+ def test_reset_table_with_non_integer_pk
50
+ Subscriber.delete_all
51
+ Subscriber.connection.reset_pk_sequence! 'subscribers'
52
+
53
+ sub = Subscriber.new(:name => 'robert drake')
54
+ sub.id = 'bob drake'
55
+ assert sub.save!
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,33 @@
1
+ require 'abstract_unit'
2
+ require "#{File.dirname(__FILE__)}/../lib/active_record/schema"
3
+
4
+ if ActiveRecord::Base.connection.supports_migrations?
5
+
6
+ class ActiveRecordSchemaTest < Test::Unit::TestCase
7
+ self.use_transactional_fixtures = false
8
+
9
+ def setup
10
+ @connection = ActiveRecord::Base.connection
11
+ end
12
+
13
+ def teardown
14
+ @connection.drop_table :fruits rescue nil
15
+ end
16
+
17
+ def test_schema_define
18
+ ActiveRecord::Schema.define(:version => 7) do
19
+ create_table :fruits do |t|
20
+ t.column :color, :string
21
+ t.column :size, :string
22
+ t.column :texture, :string
23
+ t.column :flavor, :string
24
+ end
25
+ end
26
+
27
+ assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" }
28
+ assert_nothing_raised { @connection.select_all "SELECT * FROM schema_info" }
29
+ assert_equal 7, @connection.select_one("SELECT version FROM schema_info")['version'].to_i
30
+ end
31
+ end
32
+
33
+ end
@@ -83,6 +83,20 @@ class AssociationCallbacksTest < Test::Unit::TestCase
83
83
  assert_equal ["before_removing#{david.id}", "after_removing#{david.id}", "before_removing#{jamis.id}",
84
84
  "after_removing#{jamis.id}"], activerecord.developers_log
85
85
  end
86
+
87
+ def test_has_and_belongs_to_many_remove_callback_on_clear
88
+ activerecord = projects(:active_record)
89
+ assert activerecord.developers_log.empty?
90
+ if activerecord.developers_with_callbacks.size == 0
91
+ activerecord.developers << developers(:david)
92
+ activerecord.developers << developers(:jamis)
93
+ activerecord.reload
94
+ assert activerecord.developers_with_callbacks.size == 2
95
+ end
96
+ log_array = activerecord.developers_with_callbacks.collect {|d| ["before_removing#{d.id}","after_removing#{d.id}"]}.flatten.sort
97
+ assert activerecord.developers_with_callbacks.clear
98
+ assert_equal log_array, activerecord.developers_log.sort
99
+ end
86
100
 
87
101
  def test_dont_add_if_before_callback_raises_exception
88
102
  assert !@david.unchangable_posts.include?(@authorless)
@@ -20,14 +20,19 @@ class EagerAssociationTest < Test::Unit::TestCase
20
20
  assert post.comments.include?(comments(:greetings))
21
21
  end
22
22
 
23
+ def test_loading_conditions_with_or
24
+ posts = authors(:david).posts.find(:all, :include => :comments, :conditions => "comments.body like 'Normal%' OR comments.type = 'SpecialComment'")
25
+ assert_nil posts.detect { |p| p.author_id != authors(:david).id },
26
+ "expected to find only david's posts"
27
+ end
28
+
23
29
  def test_with_ordering
24
- posts = Post.find(:all, :include => :comments, :order => "posts.id DESC")
25
- assert_equal posts(:sti_habtm), posts[0]
26
- assert_equal posts(:sti_post_and_comments), posts[1]
27
- assert_equal posts(:sti_comments), posts[2]
28
- assert_equal posts(:authorless), posts[3]
29
- assert_equal posts(:thinking), posts[4]
30
- assert_equal posts(:welcome), posts[5]
30
+ list = Post.find(:all, :include => :comments, :order => "posts.id DESC")
31
+ [:eager_other, :sti_habtm, :sti_post_and_comments, :sti_comments,
32
+ :authorless, :thinking, :welcome
33
+ ].each_with_index do |post, index|
34
+ assert_equal posts(post), list[index]
35
+ end
31
36
  end
32
37
 
33
38
  def test_loading_with_multiple_associations
@@ -48,32 +53,32 @@ class EagerAssociationTest < Test::Unit::TestCase
48
53
 
49
54
  def test_eager_association_loading_with_belongs_to
50
55
  comments = Comment.find(:all, :include => :post)
51
- assert_equal 9, comments.length
56
+ assert_equal 10, comments.length
52
57
  titles = comments.map { |c| c.post.title }
53
58
  assert titles.include?(posts(:welcome).title)
54
59
  assert titles.include?(posts(:sti_post_and_comments).title)
55
60
  end
56
61
 
57
62
  def test_eager_association_loading_with_belongs_to_and_limit
58
- comments = Comment.find(:all, :include => :post, :limit => 5)
63
+ comments = Comment.find(:all, :include => :post, :limit => 5, :order => 'comments.id')
59
64
  assert_equal 5, comments.length
60
65
  assert_equal [1,2,3,5,6], comments.collect { |c| c.id }
61
66
  end
62
67
 
63
68
  def test_eager_association_loading_with_belongs_to_and_limit_and_conditions
64
- comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 4', :limit => 3)
69
+ comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 4', :limit => 3, :order => 'comments.id')
65
70
  assert_equal 3, comments.length
66
71
  assert_equal [5,6,7], comments.collect { |c| c.id }
67
72
  end
68
73
 
69
74
  def test_eager_association_loading_with_belongs_to_and_limit_and_offset
70
- comments = Comment.find(:all, :include => :post, :limit => 3, :offset => 2)
75
+ comments = Comment.find(:all, :include => :post, :limit => 3, :offset => 2, :order => 'comments.id')
71
76
  assert_equal 3, comments.length
72
77
  assert_equal [3,5,6], comments.collect { |c| c.id }
73
78
  end
74
79
 
75
80
  def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions
76
- comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 4', :limit => 3, :offset => 1)
81
+ comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 4', :limit => 3, :offset => 1, :order => 'comments.id')
77
82
  assert_equal 3, comments.length
78
83
  assert_equal [6,7,8], comments.collect { |c| c.id }
79
84
  end
@@ -90,8 +95,35 @@ class EagerAssociationTest < Test::Unit::TestCase
90
95
  assert_equal [], posts
91
96
  end
92
97
 
93
- def test_eager_association_raise_on_limit
94
- assert_raises(ActiveRecord::ConfigurationError) { Post.find(:all, :include => [:author, :comments], :limit => 1) }
98
+ def test_eager_with_has_many_and_limit
99
+ posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2)
100
+ assert_equal 2, posts.size
101
+ assert_equal 3, posts.inject(0) { |sum, post| sum += post.comments.size }
102
+ end
103
+
104
+ def test_eager_with_has_many_and_limit_with_no_results
105
+ posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "posts.title = 'magic forest'")
106
+ assert_equal 0, posts.size
107
+ end
108
+
109
+ def test_eager_with_has_and_belongs_to_many_and_limit
110
+ posts = Post.find(:all, :include => :categories, :order => "posts.id", :limit => 3)
111
+ assert_equal 3, posts.size
112
+ assert_equal 2, posts[0].categories.size
113
+ assert_equal 1, posts[1].categories.size
114
+ assert_equal 0, posts[2].categories.size
115
+ assert posts[0].categories.include?(categories(:technology))
116
+ assert posts[1].categories.include?(categories(:general))
117
+ end
118
+
119
+ def test_eager_with_has_many_and_limit_and_conditions_on_the_eagers
120
+ assert_raises(ArgumentError) do
121
+ posts = authors(:david).posts.find(:all,
122
+ :include => :comments,
123
+ :conditions => "comments.body like 'Normal%' OR comments.type = 'SpecialComment'",
124
+ :limit => 2
125
+ )
126
+ end
95
127
  end
96
128
 
97
129
  def test_eager_association_loading_with_habtm
@@ -136,12 +168,22 @@ class EagerAssociationTest < Test::Unit::TestCase
136
168
  end
137
169
 
138
170
  def test_eager_with_invalid_association_reference
171
+ assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
172
+ post = Post.find(6, :include=> :monkeys )
173
+ }
139
174
  assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
140
175
  post = Post.find(6, :include=>[ :monkeys ])
141
176
  }
177
+ assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
178
+ post = Post.find(6, :include=>[ 'monkeys' ])
179
+ }
142
180
  assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") {
143
181
  post = Post.find(6, :include=>[ :monkeys, :elephants ])
144
182
  }
145
183
  end
146
184
 
185
+ def test_eager_with_valid_association_as_string_not_symbol
186
+ assert_nothing_raised { Post.find(:all, :include => 'comments') }
187
+ end
188
+
147
189
  end