activerecord 1.0.0

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 (106) hide show
  1. data/CHANGELOG +581 -0
  2. data/README +361 -0
  3. data/RUNNING_UNIT_TESTS +36 -0
  4. data/dev-utils/eval_debugger.rb +9 -0
  5. data/examples/associations.png +0 -0
  6. data/examples/associations.rb +87 -0
  7. data/examples/shared_setup.rb +15 -0
  8. data/examples/validation.rb +88 -0
  9. data/install.rb +60 -0
  10. data/lib/active_record.rb +48 -0
  11. data/lib/active_record/aggregations.rb +165 -0
  12. data/lib/active_record/associations.rb +536 -0
  13. data/lib/active_record/associations/association_collection.rb +70 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -0
  15. data/lib/active_record/associations/has_many_association.rb +104 -0
  16. data/lib/active_record/base.rb +985 -0
  17. data/lib/active_record/callbacks.rb +337 -0
  18. data/lib/active_record/connection_adapters/abstract_adapter.rb +326 -0
  19. data/lib/active_record/connection_adapters/mysql_adapter.rb +131 -0
  20. data/lib/active_record/connection_adapters/postgresql_adapter.rb +177 -0
  21. data/lib/active_record/connection_adapters/sqlite_adapter.rb +107 -0
  22. data/lib/active_record/deprecated_associations.rb +70 -0
  23. data/lib/active_record/fixtures.rb +172 -0
  24. data/lib/active_record/observer.rb +71 -0
  25. data/lib/active_record/reflection.rb +126 -0
  26. data/lib/active_record/support/class_attribute_accessors.rb +43 -0
  27. data/lib/active_record/support/class_inheritable_attributes.rb +37 -0
  28. data/lib/active_record/support/clean_logger.rb +10 -0
  29. data/lib/active_record/support/inflector.rb +70 -0
  30. data/lib/active_record/transactions.rb +102 -0
  31. data/lib/active_record/validations.rb +205 -0
  32. data/lib/active_record/vendor/mysql.rb +1117 -0
  33. data/lib/active_record/vendor/simple.rb +702 -0
  34. data/lib/active_record/wrappers/yaml_wrapper.rb +15 -0
  35. data/lib/active_record/wrappings.rb +59 -0
  36. data/rakefile +122 -0
  37. data/test/abstract_unit.rb +16 -0
  38. data/test/aggregations_test.rb +34 -0
  39. data/test/all.sh +8 -0
  40. data/test/associations_test.rb +477 -0
  41. data/test/base_test.rb +513 -0
  42. data/test/class_inheritable_attributes_test.rb +33 -0
  43. data/test/connections/native_mysql/connection.rb +24 -0
  44. data/test/connections/native_postgresql/connection.rb +24 -0
  45. data/test/connections/native_sqlite/connection.rb +24 -0
  46. data/test/deprecated_associations_test.rb +336 -0
  47. data/test/finder_test.rb +67 -0
  48. data/test/fixtures/accounts/signals37 +3 -0
  49. data/test/fixtures/accounts/unknown +2 -0
  50. data/test/fixtures/auto_id.rb +4 -0
  51. data/test/fixtures/column_name.rb +3 -0
  52. data/test/fixtures/companies/first_client +6 -0
  53. data/test/fixtures/companies/first_firm +4 -0
  54. data/test/fixtures/companies/second_client +6 -0
  55. data/test/fixtures/company.rb +37 -0
  56. data/test/fixtures/company_in_module.rb +33 -0
  57. data/test/fixtures/course.rb +3 -0
  58. data/test/fixtures/courses/java +2 -0
  59. data/test/fixtures/courses/ruby +2 -0
  60. data/test/fixtures/customer.rb +30 -0
  61. data/test/fixtures/customers/david +6 -0
  62. data/test/fixtures/db_definitions/mysql.sql +96 -0
  63. data/test/fixtures/db_definitions/mysql2.sql +4 -0
  64. data/test/fixtures/db_definitions/postgresql.sql +113 -0
  65. data/test/fixtures/db_definitions/postgresql2.sql +4 -0
  66. data/test/fixtures/db_definitions/sqlite.sql +85 -0
  67. data/test/fixtures/db_definitions/sqlite2.sql +4 -0
  68. data/test/fixtures/default.rb +2 -0
  69. data/test/fixtures/developer.rb +8 -0
  70. data/test/fixtures/developers/david +2 -0
  71. data/test/fixtures/developers/jamis +2 -0
  72. data/test/fixtures/developers_projects/david_action_controller +2 -0
  73. data/test/fixtures/developers_projects/david_active_record +2 -0
  74. data/test/fixtures/developers_projects/jamis_active_record +2 -0
  75. data/test/fixtures/entrant.rb +3 -0
  76. data/test/fixtures/entrants/first +3 -0
  77. data/test/fixtures/entrants/second +3 -0
  78. data/test/fixtures/entrants/third +3 -0
  79. data/test/fixtures/fixture_database.sqlite +0 -0
  80. data/test/fixtures/fixture_database_2.sqlite +0 -0
  81. data/test/fixtures/movie.rb +5 -0
  82. data/test/fixtures/movies/first +2 -0
  83. data/test/fixtures/movies/second +2 -0
  84. data/test/fixtures/project.rb +3 -0
  85. data/test/fixtures/projects/action_controller +2 -0
  86. data/test/fixtures/projects/active_record +2 -0
  87. data/test/fixtures/reply.rb +21 -0
  88. data/test/fixtures/subscriber.rb +5 -0
  89. data/test/fixtures/subscribers/first +2 -0
  90. data/test/fixtures/subscribers/second +2 -0
  91. data/test/fixtures/topic.rb +20 -0
  92. data/test/fixtures/topics/first +9 -0
  93. data/test/fixtures/topics/second +8 -0
  94. data/test/fixtures_test.rb +20 -0
  95. data/test/inflector_test.rb +104 -0
  96. data/test/inheritance_test.rb +125 -0
  97. data/test/lifecycle_test.rb +110 -0
  98. data/test/modules_test.rb +21 -0
  99. data/test/multiple_db_test.rb +46 -0
  100. data/test/pk_test.rb +57 -0
  101. data/test/reflection_test.rb +78 -0
  102. data/test/thread_safety_test.rb +33 -0
  103. data/test/transactions_test.rb +83 -0
  104. data/test/unconnected_test.rb +24 -0
  105. data/test/validations_test.rb +126 -0
  106. metadata +166 -0
@@ -0,0 +1,21 @@
1
+ require 'abstract_unit'
2
+ # require File.dirname(__FILE__) + '/../dev-utils/eval_debugger'
3
+ require 'fixtures/company_in_module'
4
+
5
+ class ModulesTest < Test::Unit::TestCase
6
+ def setup
7
+ create_fixtures "accounts"
8
+ create_fixtures "companies"
9
+ end
10
+
11
+ def test_module_spanning_associations
12
+ assert MyApplication::Business::Firm.find_first.has_clients?, "Firm should have clients"
13
+ firm = MyApplication::Business::Firm.find_first
14
+ assert_nil firm.class.table_name.match('::'), "Firm shouldn't have the module appear in its table name"
15
+ assert_equal 2, firm.clients_count, "Firm should have two clients"
16
+ end
17
+
18
+ def test_associations_spanning_cross_modules
19
+ assert MyApplication::Billing::Account.find(1).has_firm?, "37signals account should be able to backtrack"
20
+ end
21
+ end
@@ -0,0 +1,46 @@
1
+ require 'abstract_unit'
2
+ require 'fixtures/course'
3
+ require 'fixtures/entrant'
4
+
5
+ class MultipleDbTest < Test::Unit::TestCase
6
+ def setup
7
+ @courses = create_fixtures("courses") { Course.retrieve_connection }
8
+ @entrants = create_fixtures("entrants")
9
+ end
10
+
11
+ def test_connected
12
+ assert_not_nil Entrant.connection
13
+ assert_not_nil Course.connection
14
+ end
15
+
16
+ def test_proper_connection
17
+ assert_not_equal(Entrant.connection, Course.connection)
18
+ assert_equal(Entrant.connection, Entrant.retrieve_connection)
19
+ assert_equal(Course.connection, Course.retrieve_connection)
20
+ assert_equal(ActiveRecord::Base.connection, Entrant.connection)
21
+ end
22
+
23
+ def test_find
24
+ c1 = Course.find(1)
25
+ assert_equal "Ruby Development", c1.name
26
+ c2 = Course.find(2)
27
+ assert_equal "Java Development", c2.name
28
+ e1 = Entrant.find(1)
29
+ assert_equal "Ruby Developer", e1.name
30
+ e2 = Entrant.find(2)
31
+ assert_equal "Ruby Guru", e2.name
32
+ e3 = Entrant.find(3)
33
+ assert_equal "Java Lover", e3.name
34
+ end
35
+
36
+ def test_associations
37
+ c1 = Course.find(1)
38
+ assert_equal 2, c1.entrants_count
39
+ e1 = Entrant.find(1)
40
+ assert_equal e1.course.id, c1.id
41
+ c2 = Course.find(2)
42
+ assert_equal 1, c2.entrants_count
43
+ e3 = Entrant.find(3)
44
+ assert_equal e3.course.id, c2.id
45
+ end
46
+ end
@@ -0,0 +1,57 @@
1
+ require 'abstract_unit'
2
+ require 'fixtures/topic'
3
+ require 'fixtures/subscriber'
4
+ require 'fixtures/movie'
5
+
6
+ class PrimaryKeysTest < Test::Unit::TestCase
7
+ def setup
8
+ @topics = create_fixtures "topics"
9
+ @subscribers = create_fixtures "subscribers"
10
+ @movies = create_fixtures "movies"
11
+ end
12
+
13
+ def test_integer_key
14
+ topic = Topic.find(1)
15
+ assert_equal(@topics["first"]["author_name"], topic.author_name)
16
+ topic = Topic.find(2)
17
+ assert_equal(@topics["second"]["author_name"], topic.author_name)
18
+
19
+ topic = Topic.new
20
+ topic.title = "New Topic"
21
+ topic.save
22
+ id = topic.id
23
+
24
+ topicReloaded = Topic.find(id)
25
+ assert_equal("New Topic", topicReloaded.title)
26
+ end
27
+
28
+ def test_string_key
29
+ subscriber = Subscriber.find(@subscribers["first"]["nick"])
30
+ assert_equal(@subscribers["first"]["name"], subscriber.name)
31
+ subscriber = Subscriber.find(@subscribers["second"]["nick"])
32
+ assert_equal(@subscribers["second"]["name"], subscriber.name)
33
+
34
+ subscriber = Subscriber.new
35
+ subscriber.id = "jdoe"
36
+ subscriber.name = "John Doe"
37
+ subscriber.save
38
+
39
+ subscriberReloaded = Subscriber.find("jdoe")
40
+ assert_equal("John Doe", subscriberReloaded.name)
41
+ end
42
+
43
+ def test_find_with_more_than_one_string_key
44
+ assert_equal 2, Subscriber.find(@subscribers["first"]["nick"], @subscribers["second"]["nick"]).length
45
+ end
46
+
47
+ def test_primary_key_prefix
48
+ ActiveRecord::Base.primary_key_prefix_type = :table_name
49
+ assert_equal "topicid", Topic.primary_key
50
+
51
+ ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore
52
+ assert_equal "topic_id", Topic.primary_key
53
+
54
+ ActiveRecord::Base.primary_key_prefix_type = nil
55
+ assert_equal "id", Topic.primary_key
56
+ end
57
+ end
@@ -0,0 +1,78 @@
1
+ #require File.dirname(__FILE__) + '/../dev-utils/eval_debugger'
2
+ require 'abstract_unit'
3
+ require 'fixtures/topic'
4
+ require 'fixtures/customer'
5
+ require 'fixtures/company'
6
+ require 'fixtures/company_in_module'
7
+
8
+ class ReflectionTest < Test::Unit::TestCase
9
+ def setup
10
+ @topics = create_fixtures "topics"
11
+ @customers = create_fixtures "customers"
12
+ @companies = create_fixtures "companies"
13
+ @first = Topic.find(1)
14
+ end
15
+
16
+ def test_read_attribute_names
17
+ assert_equal(
18
+ %w( id title author_name author_email_address written_on last_read content approved replies_count parent_id type ).sort,
19
+ @first.attribute_names
20
+ )
21
+ end
22
+
23
+ def test_columns
24
+ assert_equal 11, Topic.columns.length
25
+ end
26
+
27
+ def test_content_columns
28
+ assert_equal 7, Topic.content_columns.length
29
+ end
30
+
31
+ def test_column_string_type_and_limit
32
+ assert_equal :string, @first.column_for_attribute("title").type
33
+ assert_equal 255, @first.column_for_attribute("title").limit
34
+ end
35
+
36
+ def test_human_name_for_column
37
+ assert_equal "Author name", @first.column_for_attribute("author_name").human_name
38
+ end
39
+
40
+ def test_integer_columns
41
+ assert_equal :integer, @first.column_for_attribute("id").type
42
+ end
43
+
44
+ def test_aggregation_reflection
45
+ reflection_for_address = ActiveRecord::Reflection::AggregateReflection.new(
46
+ :address, { :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer
47
+ )
48
+
49
+ reflection_for_balance = ActiveRecord::Reflection::AggregateReflection.new(
50
+ :balance, { :class_name => "Money", :mapping => %w(balance amount) }, Customer
51
+ )
52
+
53
+ assert_equal(
54
+ [ reflection_for_address, reflection_for_balance ],
55
+ Customer.reflect_on_all_aggregations
56
+ )
57
+
58
+ assert_equal reflection_for_address, Customer.reflect_on_aggregation(:address)
59
+
60
+ assert_equal Address, Customer.reflect_on_aggregation(:address).klass
61
+ end
62
+
63
+ def test_association_reflection
64
+ reflection_for_clients = ActiveRecord::Reflection::AssociationReflection.new(
65
+ :clients, { :order => "id", :dependent => true }, Firm
66
+ )
67
+
68
+ assert_equal reflection_for_clients, Firm.reflect_on_association(:clients)
69
+
70
+ assert_equal Client, Firm.reflect_on_association(:clients).klass
71
+ assert_equal Client, Firm.reflect_on_association(:clients_of_firm).klass
72
+ end
73
+
74
+ def test_association_reflection_in_modules
75
+ assert_equal MyApplication::Business::Client, MyApplication::Business::Firm.reflect_on_association(:clients_of_firm).klass
76
+ assert_equal MyApplication::Business::Firm, MyApplication::Billing::Account.reflect_on_association(:firm).klass
77
+ end
78
+ end
@@ -0,0 +1,33 @@
1
+ require 'abstract_unit'
2
+ require 'fixtures/topic'
3
+
4
+ class ThreadSafetyTest < Test::Unit::TestCase
5
+ def setup
6
+ @topics = create_fixtures "topics"
7
+ @threads = []
8
+ end
9
+
10
+ def test_threading_on_transactions
11
+ # SQLite breaks down under thread banging
12
+ # Jamis Buck (author of SQLite-ruby): "I know that sqlite itself is not designed for concurrent access"
13
+ if ActiveRecord::ConnectionAdapters.const_defined? :SQLiteAdapter
14
+ return true if ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::SQLiteAdapter)
15
+ end
16
+
17
+ 5.times do |thread_number|
18
+ @threads << Thread.new(thread_number) do |thread_number|
19
+ first, second = Topic.find(1, 2)
20
+ Topic.transaction(first, second) do
21
+ Topic.logger.info "started #{thread_number}"
22
+ first.approved = 1
23
+ second.approved = 0
24
+ first.save
25
+ second.save
26
+ Topic.logger.info "ended #{thread_number}"
27
+ end
28
+ end
29
+ end
30
+
31
+ @threads.each { |t| t.join }
32
+ end
33
+ end
@@ -0,0 +1,83 @@
1
+ require 'abstract_unit'
2
+ require 'fixtures/topic'
3
+
4
+
5
+ class TransactionTest < Test::Unit::TestCase
6
+ def setup
7
+ @topics = create_fixtures "topics"
8
+ @first, @second = Topic.find(1, 2)
9
+ end
10
+
11
+ def test_succesful
12
+ Topic.transaction do
13
+ @first.approved = 1
14
+ @second.approved = 0
15
+ @first.save
16
+ @second.save
17
+ end
18
+
19
+ assert Topic.find(1).approved?, "First should have been approved"
20
+ assert !Topic.find(2).approved?, "Second should have been unapproved"
21
+ end
22
+
23
+ def test_failing_on_exception
24
+ begin
25
+ Topic.transaction do
26
+ @first.approved = true
27
+ @second.approved = false
28
+ @first.save
29
+ @second.save
30
+ raise "Bad things!"
31
+ end
32
+ rescue
33
+ # caught it
34
+ end
35
+
36
+ assert @first.approved?, "First should still be changed in the objects"
37
+ assert !@second.approved?, "Second should still be changed in the objects"
38
+
39
+ assert !Topic.find(1).approved?, "First shouldn't have been approved"
40
+ assert Topic.find(2).approved?, "Second should still be approved"
41
+ end
42
+
43
+ def test_failing_with_object_rollback
44
+ begin
45
+ Topic.transaction(@first, @second) do
46
+ @first.approved = true
47
+ @second.approved = false
48
+ @first.save
49
+ @second.save
50
+ raise "Bad things!"
51
+ end
52
+ rescue
53
+ # caught it
54
+ end
55
+
56
+ assert !@first.approved?, "First shouldn't have been approved"
57
+ assert @second.approved?, "Second should still be approved"
58
+ end
59
+
60
+ def test_callback_rollback_in_save
61
+ add_exception_raising_after_save_callback_to_topic
62
+
63
+ begin
64
+ @first.approved = true
65
+ @first.save
66
+ flunk
67
+ rescue => e
68
+ assert_equal "Make the transaction rollback", e.message
69
+ assert !Topic.find(1).approved?
70
+ ensure
71
+ remove_exception_raising_after_save_callback_to_topic
72
+ end
73
+ end
74
+
75
+ private
76
+ def add_exception_raising_after_save_callback_to_topic
77
+ Topic.class_eval { def after_save() raise "Make the transaction rollback" end }
78
+ end
79
+
80
+ def remove_exception_raising_after_save_callback_to_topic
81
+ Topic.class_eval { remove_method :after_save }
82
+ end
83
+ end
@@ -0,0 +1,24 @@
1
+ require 'abstract_unit'
2
+
3
+ class TestRecord < ActiveRecord::Base
4
+ end
5
+
6
+ class TestUnconnectedAdaptor < Test::Unit::TestCase
7
+
8
+ def setup
9
+ @connection = ActiveRecord::Base.remove_connection
10
+ end
11
+
12
+ def teardown
13
+ ActiveRecord::Base.establish_connection(@connection)
14
+ end
15
+
16
+ def test_unconnected
17
+ assert_raise(ActiveRecord::ConnectionNotEstablished) do
18
+ TestRecord.find(1)
19
+ end
20
+ assert_raise(ActiveRecord::ConnectionNotEstablished) do
21
+ TestRecord.new.save
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,126 @@
1
+ require 'abstract_unit'
2
+ require 'fixtures/topic'
3
+ require 'fixtures/reply'
4
+ require 'fixtures/developer'
5
+
6
+
7
+ class ValidationsTest < Test::Unit::TestCase
8
+ def setup
9
+ @topic_fixtures = create_fixtures("topics")
10
+ @developers = create_fixtures("developers")
11
+ end
12
+
13
+ def test_single_field_validation
14
+ r = Reply.new
15
+ r.title = "There's no content!"
16
+ assert !r.save, "A reply without content shouldn't be saveable"
17
+
18
+ r.content = "Messa content!"
19
+ assert r.save, "A reply with content should be saveable"
20
+ end
21
+
22
+ def test_single_attr_validation_and_error_msg
23
+ r = Reply.new
24
+ r.title = "There's no content!"
25
+ r.save
26
+ assert r.errors.invalid?("content"), "A reply without content should mark that attribute as invalid"
27
+ assert_equal "Empty", r.errors.on("content"), "A reply without content should contain an error"
28
+ assert_equal 1, r.errors.count
29
+ end
30
+
31
+ def test_double_attr_validation_and_error_msg
32
+ r = Reply.new
33
+ assert !r.save
34
+
35
+ assert r.errors.invalid?("title"), "A reply without title should mark that attribute as invalid"
36
+ assert_equal "Empty", r.errors.on("title"), "A reply without title should contain an error"
37
+
38
+ assert r.errors.invalid?("content"), "A reply without content should mark that attribute as invalid"
39
+ assert_equal "Empty", r.errors.on("content"), "A reply without content should contain an error"
40
+
41
+ assert_equal 2, r.errors.count
42
+ end
43
+
44
+ def test_error_on_create
45
+ r = Reply.new
46
+ r.title = "Wrong Create"
47
+ assert !r.save
48
+ assert r.errors.invalid?("title"), "A reply with a bad title should mark that attribute as invalid"
49
+ assert_equal "is Wrong Create", r.errors.on("title"), "A reply with a bad content should contain an error"
50
+ end
51
+
52
+
53
+ def test_error_on_update
54
+ r = Reply.new
55
+ r.title = "Bad"
56
+ r.content = "Good"
57
+
58
+ assert r.save, "First save should be successful"
59
+
60
+ r.title = "Wrong Update"
61
+ assert !r.save, "Second save should fail"
62
+
63
+ assert r.errors.invalid?("title"), "A reply with a bad title should mark that attribute as invalid"
64
+ assert_equal "is Wrong Update", r.errors.on("title"), "A reply with a bad content should contain an error"
65
+ end
66
+
67
+ def test_single_error_per_attr_iteration
68
+ r = Reply.new
69
+ r.save
70
+
71
+ errors = []
72
+ r.errors.each { |attr, msg| errors << [attr, msg] }
73
+
74
+ assert errors.include?(["title", "Empty"])
75
+ assert errors.include?(["content", "Empty"])
76
+ end
77
+
78
+ def test_multiple_errors_per_attr_iteration_with_full_error_composition
79
+ r = Reply.new
80
+ r.title = "Wrong Create"
81
+ r.content = "Mismatch"
82
+ r.save
83
+
84
+ errors = []
85
+ r.errors.each_full { |error| errors << error }
86
+
87
+ assert_equal "Title is Wrong Create", errors[0]
88
+ assert_equal "Title is Content Mismatch", errors[1]
89
+ assert_equal 2, r.errors.count
90
+ end
91
+
92
+ def test_errors_on_base
93
+ r = Reply.new
94
+ r.content = "Mismatch"
95
+ r.save
96
+ r.errors.add_to_base "Reply is not dignifying"
97
+
98
+ errors = []
99
+ r.errors.each_full { |error| errors << error }
100
+
101
+ assert_equal "Reply is not dignifying", r.errors.on_base
102
+
103
+ assert errors.include?("Title Empty")
104
+ assert errors.include?("Reply is not dignifying")
105
+ assert_equal 2, r.errors.count
106
+ end
107
+
108
+ def test_create_without_validation
109
+ reply = Reply.new
110
+ assert !reply.save
111
+ assert reply.save(false)
112
+ end
113
+
114
+ def test_errors_on_boundary_breaking
115
+ developer = Developer.new("name" => "xs")
116
+ assert !developer.save
117
+ assert_equal "is too short (min is 3 characters)", developer.errors.on("name")
118
+
119
+ developer.name = "All too very long for this boundary, it really is"
120
+ assert !developer.save
121
+ assert_equal "is too long (max is 20 characters)", developer.errors.on("name")
122
+
123
+ developer.name = "Just right"
124
+ assert developer.save
125
+ end
126
+ end