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
@@ -2,53 +2,36 @@ require 'abstract_unit'
2
2
  require 'fixtures/binary'
3
3
 
4
4
  class BinaryTest < Test::Unit::TestCase
5
+ BINARY_FIXTURE_PATH = File.dirname(__FILE__) + '/fixtures/flowers.jpg'
6
+
5
7
  def setup
6
- @data = create_data_fixture
8
+ Binary.connection.execute 'DELETE FROM binaries'
9
+ @data = File.read(BINARY_FIXTURE_PATH).freeze
7
10
  end
8
11
 
9
- def test_load_save
10
- # Without using prepared statements, it makes no sense to test
11
- # BLOB data with SQL Server, because the length of a statement is
12
- # limited to 8KB.
13
- if ActiveRecord::ConnectionAdapters.const_defined? :SQLServerAdapter
14
- return true if ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::SQLServerAdapter)
15
- end
16
-
17
- # Without using prepared statements, it makes no sense to test
18
- # BLOB data with DB2, because the length of a statement is
19
- # limited to 32KB.
20
- if ActiveRecord::ConnectionAdapters.const_defined? :DB2Adapter
21
- return true if ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::DB2Adapter)
22
- end
23
-
24
- if ActiveRecord::ConnectionAdapters.const_defined? :OracleAdapter
25
- return true if ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::OracleAdapter)
26
- end
27
- bin = Binary.new
28
- bin.data = @data
29
-
30
- assert bin.data == @data,
31
- "Assigned data differs from file data"
32
-
33
- bin.save
12
+ def test_truth
13
+ assert true
14
+ end
34
15
 
35
- assert bin.data == @data,
36
- "Assigned data differs from file data after save"
16
+ # Without using prepared statements, it makes no sense to test
17
+ # BLOB data with SQL Server, because the length of a statement is
18
+ # limited to 8KB.
19
+ #
20
+ # Without using prepared statements, it makes no sense to test
21
+ # BLOB data with DB2, because the length of a statement is
22
+ # limited to 32KB.
23
+ unless %w(SQLServer DB2 OCI).include? ActiveRecord::Base.connection.adapter_name
24
+ def test_load_save
25
+ bin = Binary.new
26
+ bin.data = @data
37
27
 
38
- db_bin = Binary.find(bin.id)
28
+ assert @data == bin.data, 'Newly assigned data differs from original'
29
+
30
+ bin.save
31
+ assert @data == bin.data, 'Data differs from original after save'
39
32
 
40
- assert db_bin.data == bin.data,
41
- "Loaded binary data differs from memory version"
42
-
43
- assert db_bin.data == File.new(File.dirname(__FILE__)+"/fixtures/associations.png","rb").read,
44
- "Loaded binary data differs from file version"
45
- end
46
-
47
- private
48
-
49
- def create_data_fixture
50
- Binary.connection.execute("DELETE FROM binaries")
51
- File.new(File.dirname(__FILE__)+"/fixtures/associations.png","rb").read
33
+ db_bin = Binary.find(bin.id)
34
+ assert @data == db_bin.data, 'Reloaded data differs from original'
35
+ end
52
36
  end
53
-
54
37
  end
@@ -70,6 +70,44 @@ class RecursiveCallbackDeveloper < ActiveRecord::Base
70
70
  end
71
71
  end
72
72
 
73
+ class ImmutableDeveloper < ActiveRecord::Base
74
+ set_table_name 'developers'
75
+
76
+ validates_inclusion_of :salary, :in => 50000..200000
77
+
78
+ before_save :cancel
79
+ before_destroy :cancel
80
+
81
+ def cancelled?
82
+ @cancelled == true
83
+ end
84
+
85
+ private
86
+ def cancel
87
+ @cancelled = true
88
+ false
89
+ end
90
+ end
91
+
92
+ class ImmutableMethodDeveloper < ActiveRecord::Base
93
+ set_table_name 'developers'
94
+
95
+ validates_inclusion_of :salary, :in => 50000..200000
96
+
97
+ def cancelled?
98
+ @cancelled == true
99
+ end
100
+
101
+ def before_save
102
+ @cancelled = true
103
+ false
104
+ end
105
+
106
+ def before_destroy
107
+ @cancelled = true
108
+ false
109
+ end
110
+ end
73
111
 
74
112
  class CallbacksTest < Test::Unit::TestCase
75
113
  fixtures :developers
@@ -283,6 +321,43 @@ class CallbacksTest < Test::Unit::TestCase
283
321
  ], david.history
284
322
  end
285
323
 
324
+ def test_before_save_returning_false
325
+ david = ImmutableDeveloper.find(1)
326
+ assert david.valid?
327
+ assert david.save
328
+ assert david.cancelled?
329
+
330
+ david = ImmutableDeveloper.find(1)
331
+ david.salary = 10_000_000
332
+ assert !david.valid?
333
+ assert !david.save
334
+ assert !david.cancelled?
335
+
336
+ david = ImmutableMethodDeveloper.find(1)
337
+ assert david.valid?
338
+ assert david.save
339
+ assert david.cancelled?
340
+
341
+ david = ImmutableMethodDeveloper.find(1)
342
+ david.salary = 10_000_000
343
+ assert !david.valid?
344
+ assert !david.save
345
+ assert !david.cancelled?
346
+ end
347
+
348
+ def test_before_destroy_returning_false
349
+ david = ImmutableDeveloper.find(1)
350
+ david.destroy
351
+ assert david.cancelled?
352
+ assert_not_nil ImmutableDeveloper.find_by_id(1)
353
+
354
+ david = ImmutableMethodDeveloper.find(1)
355
+ david.destroy
356
+ assert david.cancelled?
357
+ assert_not_nil ImmutableMethodDeveloper.find_by_id(1)
358
+ end
359
+
360
+
286
361
  def test_zzz_callback_returning_false # must be run last since we modify CallbackDeveloper
287
362
  david = CallbackDeveloper.find(1)
288
363
  CallbackDeveloper.before_validation proc { |model| model.history << [:before_validation, :returning_false]; return false }
@@ -0,0 +1,136 @@
1
+ require 'abstract_unit'
2
+ require 'fixtures/developer'
3
+ require 'fixtures/comment'
4
+ require 'fixtures/post'
5
+ require 'fixtures/category'
6
+
7
+ class ConditionsScopingTest < Test::Unit::TestCase
8
+ fixtures :developers
9
+
10
+ def test_set_conditions
11
+ Developer.constrain(:conditions => 'just a test...') do
12
+ assert_equal 'just a test...', Thread.current[:constraints][Developer][:conditions]
13
+ end
14
+ end
15
+
16
+ def test_scoped_find
17
+ Developer.constrain(:conditions => "name = 'David'") do
18
+ assert_nothing_raised { Developer.find(1) }
19
+ end
20
+ end
21
+
22
+ def test_scoped_find_first
23
+ Developer.constrain(:conditions => "salary = 100000") do
24
+ assert_equal Developer.find(10), Developer.find(:first, :order => 'name')
25
+ end
26
+ end
27
+
28
+ def test_scoped_find_all
29
+ Developer.constrain(:conditions => "name = 'David'") do
30
+ assert_equal [Developer.find(1)], Developer.find(:all)
31
+ end
32
+ end
33
+
34
+ def test_scoped_count
35
+ Developer.constrain(:conditions => "name = 'David'") do
36
+ assert_equal 1, Developer.count
37
+ end
38
+
39
+ Developer.constrain(:conditions => 'salary = 100000') do
40
+ assert_equal 8, Developer.count
41
+ assert_equal 1, Developer.count("name LIKE 'fixture_1%'")
42
+ end
43
+ end
44
+ end
45
+
46
+ class HasManyScopingTest< Test::Unit::TestCase
47
+ fixtures :comments, :posts
48
+
49
+ def setup
50
+ @welcome = Post.find(1)
51
+ end
52
+
53
+ def test_forwarding_of_static_methods
54
+ assert_equal 'a comment...', Comment.what_are_you
55
+ assert_equal 'a comment...', @welcome.comments.what_are_you
56
+ end
57
+
58
+ def test_forwarding_to_scoped
59
+ assert_equal 4, Comment.search_by_type('Comment').size
60
+ assert_equal 2, @welcome.comments.search_by_type('Comment').size
61
+ end
62
+
63
+ def test_forwarding_to_dynamic_finders
64
+ assert_equal 4, Comment.find_all_by_type('Comment').size
65
+ assert_equal 2, @welcome.comments.find_all_by_type('Comment').size
66
+ end
67
+
68
+ end
69
+
70
+
71
+ class HasAndBelongsToManyScopingTest< Test::Unit::TestCase
72
+ fixtures :posts, :categories
73
+
74
+ def setup
75
+ @welcome = Post.find(1)
76
+ end
77
+
78
+ def test_forwarding_of_static_methods
79
+ assert_equal 'a category...', Category.what_are_you
80
+ assert_equal 'a category...', @welcome.categories.what_are_you
81
+ end
82
+
83
+ def test_forwarding_to_dynamic_finders
84
+ assert_equal 1, Category.find_all_by_type('SpecialCategory').size
85
+ assert_equal 0, @welcome.categories.find_all_by_type('SpecialCategory').size
86
+ assert_equal 2, @welcome.categories.find_all_by_type('Category').size
87
+ end
88
+
89
+ end
90
+
91
+
92
+ =begin
93
+ # We disabled the scoping for has_one and belongs_to as we can't think of a proper use case
94
+
95
+
96
+ class BelongsToScopingTest< Test::Unit::TestCase
97
+ fixtures :comments, :posts
98
+
99
+ def setup
100
+ @greetings = Comment.find(1)
101
+ end
102
+
103
+ def test_forwarding_of_static_method
104
+ assert_equal 'a post...', Post.what_are_you
105
+ assert_equal 'a post...', @greetings.post.what_are_you
106
+ end
107
+
108
+ def test_forwarding_to_dynamic_finders
109
+ assert_equal 4, Post.find_all_by_type('Post').size
110
+ assert_equal 1, @greetings.post.find_all_by_type('Post').size
111
+ end
112
+
113
+ end
114
+
115
+
116
+ class HasOneScopingTest< Test::Unit::TestCase
117
+ fixtures :comments, :posts
118
+
119
+ def setup
120
+ @sti_comments = Post.find(4)
121
+ end
122
+
123
+ def test_forwarding_of_static_methods
124
+ assert_equal 'a comment...', Comment.what_are_you
125
+ assert_equal 'a very special comment...', @sti_comments.very_special_comment.what_are_you
126
+ end
127
+
128
+ def test_forwarding_to_dynamic_finders
129
+ assert_equal 1, Comment.find_all_by_type('VerySpecialComment').size
130
+ assert_equal 1, @sti_comments.very_special_comment.find_all_by_type('VerySpecialComment').size
131
+ assert_equal 0, @sti_comments.very_special_comment.find_all_by_type('Comment').size
132
+ end
133
+
134
+ end
135
+
136
+ =end
@@ -9,16 +9,12 @@ db2 = 'activerecord_unittest2'
9
9
 
10
10
  ActiveRecord::Base.establish_connection(
11
11
  :adapter => "mysql",
12
- :host => "localhost",
13
12
  :username => "rails",
14
- :password => "",
15
13
  :database => db1
16
14
  )
17
15
 
18
16
  Course.establish_connection(
19
17
  :adapter => "mysql",
20
- :host => "localhost",
21
18
  :username => "rails",
22
- :password => "",
23
19
  :database => db2
24
20
  )
@@ -0,0 +1,17 @@
1
+ print "Using native SQLite3\n"
2
+ require 'fixtures/course'
3
+ require 'logger'
4
+ ActiveRecord::Base.logger = Logger.new("debug.log")
5
+
6
+ class SqliteError < StandardError
7
+ end
8
+
9
+ def make_connection(clazz, db_definitions_file)
10
+ clazz.establish_connection(:adapter => 'sqlite3', :dbfile => ':memory:')
11
+ File.read("#{File.dirname(__FILE__)}/../../fixtures/db_definitions/#{db_definitions_file}").split(';').each do |command|
12
+ clazz.connection.execute(command) unless command.strip.empty?
13
+ end
14
+ end
15
+
16
+ make_connection(ActiveRecord::Base, 'sqlite.sql')
17
+ make_connection(Course, 'sqlite2.sql')
@@ -0,0 +1,64 @@
1
+ require 'abstract_unit'
2
+
3
+ class CopyTableTest < Test::Unit::TestCase
4
+ fixtures :companies, :comments
5
+
6
+ def setup
7
+ @connection = ActiveRecord::Base.connection
8
+ class << @connection
9
+ public :copy_table, :table_structure, :indexes
10
+ end
11
+ end
12
+
13
+ def test_copy_table(from = 'companies', to = 'companies2', options = {})
14
+ assert_nothing_raised {copy_table(from, to, options)}
15
+ assert_equal row_count(from), row_count(to)
16
+
17
+ if block_given?
18
+ yield from, to, options
19
+ else
20
+ assert_equal column_names(from), column_names(to)
21
+ end
22
+
23
+ @connection.drop_table(to) rescue nil
24
+ end
25
+
26
+ def test_copy_table_renaming_column
27
+ test_copy_table('companies', 'companies2',
28
+ :rename => {'client_of' => 'fan_of'}) do |from, to, options|
29
+ assert_equal column_values(from, 'client_of').compact.sort,
30
+ column_values(to, 'fan_of').compact.sort
31
+ end
32
+ end
33
+
34
+ def test_copy_table_with_index
35
+ test_copy_table('comments', 'comments_with_index') do
36
+ @connection.add_index('comments_with_index', ['post_id', 'type'])
37
+ test_copy_table('comments_with_index', 'comments_with_index2') do
38
+ assert_equal table_indexes_without_name('comments_with_index'),
39
+ table_indexes_without_name('comments_with_index2')
40
+ end
41
+ end
42
+ end
43
+
44
+ protected
45
+ def copy_table(from, to, options = {})
46
+ @connection.copy_table(from, to, {:temporary => true}.merge(options))
47
+ end
48
+
49
+ def column_names(table)
50
+ @connection.table_structure(table).map {|column| column['name']}
51
+ end
52
+
53
+ def column_values(table, column)
54
+ @connection.select_all("SELECT #{column} FROM #{table}").map {|row| row[column]}
55
+ end
56
+
57
+ def table_indexes_without_name(table)
58
+ @connection.indexes('comments_with_index').delete(:name)
59
+ end
60
+
61
+ def row_count(table)
62
+ @connection.select_one("SELECT COUNT(*) AS count FROM #{table}")['count']
63
+ end
64
+ end
@@ -9,7 +9,7 @@ require 'fixtures/reply'
9
9
  bad_collection_keys = false
10
10
  begin
11
11
  class Car < ActiveRecord::Base; has_many :wheels, :name => "wheels"; end
12
- rescue ActiveRecord::ActiveRecordError
12
+ rescue ArgumentError
13
13
  bad_collection_keys = true
14
14
  end
15
15
  raise "ActiveRecord should have barked on bad collection keys" unless bad_collection_keys
@@ -79,10 +79,11 @@ class DeprecatedAssociationsTest < Test::Unit::TestCase
79
79
  end
80
80
 
81
81
  def test_has_one_dependence
82
+ num_accounts = Account.count
82
83
  firm = Firm.find(1)
83
84
  assert firm.has_account?
84
- firm.destroy
85
- assert_equal 1, Account.find_all.length
85
+ firm.destroy
86
+ assert_equal num_accounts - 1, Account.count
86
87
  end
87
88
 
88
89
  def test_has_one_dependence_with_missing_association
@@ -124,10 +125,10 @@ class DeprecatedAssociationsTest < Test::Unit::TestCase
124
125
  assert !Account.find(2).firm?(companies(:first_firm)), "Unknown isn't linked"
125
126
  end
126
127
 
127
- def test_has_many_dependence_on_account
128
- assert_equal 2, Account.find_all.length
128
+ def test_has_many_dependence_on_account
129
+ num_accounts = Account.count
129
130
  companies(:first_firm).destroy
130
- assert_equal 1, Account.find_all.length
131
+ assert_equal num_accounts - 1, Account.count
131
132
  end
132
133
 
133
134
  def test_find_in
@@ -4,7 +4,7 @@ require 'fixtures/topic'
4
4
  require 'fixtures/entrant'
5
5
  require 'fixtures/developer'
6
6
 
7
- class FinderTest < Test::Unit::TestCase
7
+ class DeprecatedFinderTest < Test::Unit::TestCase
8
8
  fixtures :companies, :topics, :entrants, :developers
9
9
 
10
10
  def test_find_all_with_limit
@@ -38,8 +38,8 @@ class FinderTest < Test::Unit::TestCase
38
38
  end
39
39
 
40
40
  def test_deprecated_find_on_conditions
41
- assert Topic.find_on_conditions(1, "approved = 0")
42
- assert_raises(ActiveRecord::RecordNotFound) { Topic.find_on_conditions(1, "approved = 1") }
41
+ assert Topic.find_on_conditions(1, ["approved = ?", false])
42
+ assert_raises(ActiveRecord::RecordNotFound) { Topic.find_on_conditions(1, ["approved = ?", true]) }
43
43
  end
44
44
 
45
45
  def test_condition_interpolation