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,15 @@
1
+ require 'yaml'
2
+
3
+ module ActiveRecord
4
+ module Wrappings #:nodoc:
5
+ class YamlWrapper < AbstractWrapper #:nodoc:
6
+ def wrap(attribute) attribute.to_yaml end
7
+ def unwrap(attribute) YAML::load(attribute) end
8
+ end
9
+
10
+ module ClassMethods #:nodoc:
11
+ # Wraps the attribute in Yaml encoding
12
+ def wrap_in_yaml(*attributes) wrap_with(YamlWrapper, attributes) end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,59 @@
1
+ module ActiveRecord
2
+ # A plugin framework for wrapping attribute values before they go in and unwrapping them after they go out of the database.
3
+ # This was intended primarily for YAML wrapping of arrays and hashes, but this behavior is now native in the Base class.
4
+ # So for now this framework is laying dorment until a need pops up.
5
+ module Wrappings #:nodoc:
6
+ module ClassMethods #:nodoc:
7
+ def wrap_with(wrapper, *attributes)
8
+ [ attributes ].flat.each { |attribute| wrapper.wrap(attribute) }
9
+ end
10
+ end
11
+
12
+ def self.append_features(base)
13
+ super
14
+ base.extend(ClassMethods)
15
+ end
16
+
17
+ class AbstractWrapper #:nodoc:
18
+ def self.wrap(attribute, record_binding) #:nodoc:
19
+ %w( before_save after_save after_initialize ).each do |callback|
20
+ eval "#{callback} #{name}.new('#{attribute}')", record_binding
21
+ end
22
+ end
23
+
24
+ def initialize(attribute) #:nodoc:
25
+ @attribute = attribute
26
+ end
27
+
28
+ def save_wrapped_attribute(record) #:nodoc:
29
+ if record.attribute_present?(@attribute)
30
+ record.send(
31
+ "write_attribute",
32
+ @attribute,
33
+ wrap(record.send("read_attribute", @attribute))
34
+ )
35
+ end
36
+ end
37
+
38
+ def load_wrapped_attribute(record) #:nodoc:
39
+ if record.attribute_present?(@attribute)
40
+ record.send(
41
+ "write_attribute",
42
+ @attribute,
43
+ unwrap(record.send("read_attribute", @attribute))
44
+ )
45
+ end
46
+ end
47
+
48
+ alias_method :before_save, :save_wrapped_attribute #:nodoc:
49
+ alias_method :after_save, :load_wrapped_attribute #:nodoc:
50
+ alias_method :after_initialize, :after_save #:nodoc:
51
+
52
+ # Overwrite to implement the logic that'll take the regular attribute and wrap it.
53
+ def wrap(attribute) end
54
+
55
+ # Overwrite to implement the logic that'll take the wrapped attribute and unwrap it.
56
+ def unwrap(attribute) end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,122 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/contrib/rubyforgepublisher'
8
+
9
+ PKG_VERSION = "1.0.0"
10
+
11
+ PKG_FILES = FileList[
12
+ "lib/**/*", "test/**/*", "examples/**/*", "doc/**/*", "[A-Z]*", "install.rb", "rakefile"
13
+ ].exclude(/\bCVS\b|~$/)
14
+
15
+
16
+ desc "Default Task"
17
+ task :default => [ :test_ruby_mysql, :test_mysql_ruby, :test_sqlite, :test_postgresql ]
18
+
19
+ # Run the unit tests
20
+
21
+ Rake::TestTask.new("test_ruby_mysql") { |t|
22
+ t.libs << "test" << "test/connections/native_mysql"
23
+ t.test_files = "lib/active_record/vendor/mysql.rb"
24
+ t.pattern = 'test/*_test.rb'
25
+ t.verbose = true
26
+ }
27
+
28
+ Rake::TestTask.new("test_mysql_ruby") { |t|
29
+ t.libs << "test" << "test/connections/native_mysql"
30
+ t.pattern = 'test/*_test.rb'
31
+ t.verbose = true
32
+ }
33
+
34
+ Rake::TestTask.new("test_postgresql") { |t|
35
+ t.libs << "test" << "test/connections/native_postgresql"
36
+ t.pattern = 'test/*_test.rb'
37
+ t.verbose = true
38
+ }
39
+
40
+ Rake::TestTask.new("test_sqlite") { |t|
41
+ t.libs << "test" << "test/connections/native_sqlite"
42
+ t.pattern = 'test/*_test.rb'
43
+ t.verbose = true
44
+ }
45
+
46
+ # Genereate the RDoc documentation
47
+
48
+ Rake::RDocTask.new { |rdoc|
49
+ rdoc.rdoc_dir = 'doc'
50
+ rdoc.title = "Active Record -- Object-relation mapping put on rails"
51
+ rdoc.options << '--line-numbers --inline-source --accessor cattr_accessor=object'
52
+ rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG')
53
+ rdoc.rdoc_files.include('lib/**/*.rb')
54
+ rdoc.rdoc_files.exclude('lib/active_record/vendor/*')
55
+ rdoc.rdoc_files.include('dev-utils/*.rb')
56
+ }
57
+
58
+
59
+ # Publish documentation
60
+ desc "Publish the API documentation"
61
+ task :pdoc => [:rdoc] do
62
+ Rake::SshDirPublisher.new("davidhh@one.textdrive.com", "domains/rubyonrails.org/ar", "doc").upload
63
+ end
64
+
65
+ desc "Publish to RubyForge"
66
+ task :rubyforge do
67
+ Rake::RubyForgePublisher.new('activerecord', 'webster132').upload
68
+ end
69
+
70
+
71
+ # Create compressed packages
72
+
73
+ dist_dirs = [ "lib", "test", "examples", "dev-utils" ]
74
+
75
+ spec = Gem::Specification.new do |s|
76
+ s.name = 'activerecord'
77
+ s.version = PKG_VERSION
78
+ s.summary = "Implements the ActiveRecord pattern for ORM."
79
+ s.description = %q{Implements the ActiveRecord pattern (Fowler, PoEAA) for ORM. It ties database tables and classes together for business objects, like Customer or Subscription, that can find, save, and destroy themselves without resorting to manual SQL.}
80
+
81
+ s.files = [ "rakefile", "install.rb", "README", "RUNNING_UNIT_TESTS", "CHANGELOG" ]
82
+ dist_dirs.each do |dir|
83
+ s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "CVS" ) }
84
+ end
85
+ s.files.delete "test/fixtures/fixture_database.sqlite"
86
+ s.require_path = 'lib'
87
+ s.autorequire = 'active_record'
88
+
89
+ s.has_rdoc = true
90
+ s.extra_rdoc_files = %w( README )
91
+ s.rdoc_options.concat ['--main', 'README']
92
+
93
+ s.author = "David Heinemeier Hansson"
94
+ s.email = "david@loudthinking.com"
95
+ s.homepage = "http://activerecord.rubyonrails.org"
96
+ s.rubyforge_project = "activerecord"
97
+ end
98
+
99
+ Rake::GemPackageTask.new(spec) do |p|
100
+ p.gem_spec = spec
101
+ p.need_tar = true
102
+ p.need_zip = true
103
+ end
104
+
105
+
106
+ task :lines do
107
+ lines = 0
108
+ codelines = 0
109
+ Dir.foreach("lib/active_record") { |file_name|
110
+ next unless file_name =~ /.*rb/
111
+
112
+ f = File.open("lib/active_record/" + file_name)
113
+
114
+ while line = f.gets
115
+ lines += 1
116
+ next if line =~ /^\s*$/
117
+ next if line =~ /^\s*#/
118
+ codelines += 1
119
+ end
120
+ }
121
+ puts "Lines #{lines}, LOC #{codelines}"
122
+ end
@@ -0,0 +1,16 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
+
3
+ require 'test/unit'
4
+ require 'active_record'
5
+ require 'active_record/fixtures'
6
+ require 'connection'
7
+
8
+ class Test::Unit::TestCase #:nodoc:
9
+ def create_fixtures(*table_names)
10
+ if block_given?
11
+ Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures/", table_names) { yield }
12
+ else
13
+ Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures/", table_names)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,34 @@
1
+ require 'abstract_unit'
2
+ # require File.dirname(__FILE__) + '/../dev-utils/eval_debugger'
3
+ require 'fixtures/customer'
4
+
5
+ class AggregationsTest < Test::Unit::TestCase
6
+ def setup
7
+ @customers = create_fixtures "customers"
8
+ @david = Customer.find(1)
9
+ end
10
+
11
+ def test_find_single_value_object
12
+ assert_equal 50, @david.balance.amount
13
+ assert_kind_of Money, @david.balance
14
+ assert_equal 300, @david.balance.exchange_to("DKK").amount
15
+ end
16
+
17
+ def test_find_multiple_value_object
18
+ assert_equal @customers["david"]["address_street"], @david.address.street
19
+ assert(
20
+ @david.address.close_to?(Address.new("Different Street", @customers["david"]["address_city"], @customers["david"]["address_country"]))
21
+ )
22
+ end
23
+
24
+ def test_change_single_value_object
25
+ @david.balance = Money.new(100)
26
+ @david.save
27
+ assert_equal 100, Customer.find(1).balance.amount
28
+ end
29
+
30
+ def test_immutable_value_objects
31
+ @david.balance = Money.new(100)
32
+ assert_raises(TypeError) { @david.balance.instance_eval { @amount = 20 } }
33
+ end
34
+ end
@@ -0,0 +1,8 @@
1
+ #!/bin/sh
2
+
3
+ if [ -z "$1" ]; then
4
+ echo "Usage: $0 connections/<db_library>" 1>&2
5
+ exit 1
6
+ fi
7
+
8
+ ruby -I $1 -e 'Dir.foreach(".") { |file| require file if file =~ /_test.rb$/ }'
@@ -0,0 +1,477 @@
1
+ require 'abstract_unit'
2
+ require 'fixtures/developer'
3
+ require 'fixtures/project'
4
+ # require File.dirname(__FILE__) + '/../dev-utils/eval_debugger'
5
+ require 'fixtures/company'
6
+ require 'fixtures/topic'
7
+ require 'fixtures/reply'
8
+
9
+ # Can't declare new classes in test case methods, so tests before that
10
+ bad_collection_keys = false
11
+ begin
12
+ class Car < ActiveRecord::Base; has_many :wheels, :name => "wheels"; end
13
+ rescue ActiveRecord::ActiveRecordError
14
+ bad_collection_keys = true
15
+ end
16
+ raise "ActiveRecord should have barked on bad collection keys" unless bad_collection_keys
17
+
18
+
19
+ class AssociationsTest < Test::Unit::TestCase
20
+ def setup
21
+ create_fixtures "accounts", "companies", "accounts", "developers", "projects", "developers_projects"
22
+ @signals37 = Firm.find(1)
23
+ end
24
+
25
+ def test_force_reload
26
+ firm = Firm.new
27
+ firm.save
28
+ firm.clients.each {|c|} # forcing to load all clients
29
+ assert firm.clients.empty?, "New firm shouldn't have client objects"
30
+ assert !firm.has_clients?, "New firm shouldn't have clients"
31
+ assert_equal 0, firm.clients.size, "New firm should have 0 clients"
32
+
33
+ client = Client.new("firm_id" => firm.id)
34
+ client.save
35
+
36
+ assert firm.clients.empty?, "New firm should have cached no client objects"
37
+ assert !firm.has_clients?, "New firm should have cached a no-clients response"
38
+ assert_equal 0, firm.clients.size, "New firm should have cached 0 clients count"
39
+
40
+ assert !firm.clients(true).empty?, "New firm should have reloaded client objects"
41
+ assert_equal 1, firm.clients(true).size, "New firm should have reloaded clients count"
42
+ end
43
+
44
+ def test_storing_in_pstore
45
+ store_filename = "/tmp/ar-pstore-association-test"
46
+ File.delete(store_filename) if File.exists?(store_filename)
47
+ require "pstore"
48
+ apple = Firm.create("name" => "Apple")
49
+ natural = Client.new("name" => "Natural Company")
50
+ apple.clients << natural
51
+
52
+ db = PStore.new(store_filename)
53
+ db.transaction do
54
+ db["apple"] = apple
55
+ end
56
+
57
+ db = PStore.new(store_filename)
58
+ db.transaction do
59
+ assert_equal "Natural Company", db["apple"].clients.first.name
60
+ end
61
+ end
62
+ end
63
+
64
+ class HasOneAssociationsTest < Test::Unit::TestCase
65
+ def setup
66
+ create_fixtures "accounts", "companies", "accounts", "developers", "projects", "developers_projects"
67
+ @signals37 = Firm.find(1)
68
+ end
69
+
70
+ def test_has_one
71
+ assert_equal @signals37.account, Account.find(1)
72
+ assert_equal Account.find(1).credit_limit, @signals37.account.credit_limit
73
+ assert @signals37.has_account?, "37signals should have an account"
74
+ assert Account.find(1).firm?(@signals37), "37signals account should be able to backtrack"
75
+ assert Account.find(1).has_firm?, "37signals account should be able to backtrack"
76
+
77
+ assert !Account.find(2).has_firm?, "Unknown isn't linked"
78
+ assert !Account.find(2).firm?(@signals37), "Unknown isn't linked"
79
+ end
80
+
81
+ def test_type_mismatch
82
+ assert_raises(ActiveRecord::AssociationTypeMismatch) { @signals37.account = 1 }
83
+ assert_raises(ActiveRecord::AssociationTypeMismatch) { @signals37.account = Project.find(1) }
84
+ end
85
+
86
+ def test_natural_assignment
87
+ apple = Firm.create("name" => "Apple")
88
+ citibank = Account.create("credit_limit" => 10)
89
+ apple.account = citibank
90
+ assert_equal apple.id, citibank.firm_id
91
+ end
92
+
93
+ def test_natural_assignment_to_nil
94
+ old_account_id = @signals37.account.id
95
+ @signals37.account = nil
96
+ @signals37.save
97
+ assert_nil @signals37.account
98
+ assert_nil Account.find(old_account_id).firm_id
99
+ end
100
+
101
+ def test_build
102
+ firm = Firm.new("name" => "GlobalMegaCorp")
103
+ firm.save
104
+
105
+ account = firm.build_account("credit_limit" => 1000)
106
+ assert account.save
107
+ assert_equal account, firm.account
108
+ end
109
+
110
+ def test_failing_build_association
111
+ firm = Firm.new("name" => "GlobalMegaCorp")
112
+ firm.save
113
+
114
+ account = firm.build_account
115
+ assert !account.save
116
+ assert_equal "can't be empty", account.errors.on("credit_limit")
117
+ end
118
+
119
+ def test_create
120
+ firm = Firm.new("name" => "GlobalMegaCorp")
121
+ firm.save
122
+ assert_equal firm.create_account("credit_limit" => 1000), firm.account
123
+ end
124
+
125
+ def test_dependence
126
+ firm = Firm.find(1)
127
+ assert !firm.account.nil?
128
+ firm.destroy
129
+ assert_equal 1, Account.find_all.length
130
+ end
131
+
132
+ def test_dependence_with_missing_association
133
+ Account.destroy_all
134
+ firm = Firm.find(1)
135
+ assert !firm.has_account?
136
+ firm.destroy
137
+ end
138
+ end
139
+
140
+
141
+ class HasManyAssociationsTest < Test::Unit::TestCase
142
+ def setup
143
+ create_fixtures "accounts", "companies", "accounts", "developers", "projects", "developers_projects", "topics"
144
+ @signals37 = Firm.find(1)
145
+ end
146
+
147
+ def force_signal37_to_load_all_clients_of_firm
148
+ @signals37.clients_of_firm.each {|f| }
149
+ end
150
+
151
+ def test_finding
152
+ assert_equal 2, Firm.find_first.clients.length
153
+ end
154
+
155
+ def test_finding_default_orders
156
+ assert_equal "Summit", Firm.find_first.clients.first.name
157
+ end
158
+
159
+ def test_finding_with_different_class_name_and_order
160
+ assert_equal "Microsoft", Firm.find_first.clients_sorted_desc.first.name
161
+ end
162
+
163
+ def test_finding_with_foreign_key
164
+ assert_equal "Microsoft", Firm.find_first.clients_of_firm.first.name
165
+ end
166
+
167
+ def test_finding_with_condition
168
+ assert_equal "Microsoft", Firm.find_first.clients_like_ms.first.name
169
+ end
170
+
171
+ def test_finding_using_sql
172
+ firm = Firm.find_first
173
+ firm.clients_using_sql.first
174
+ assert_equal "Microsoft", firm.clients_using_sql.first.name
175
+ assert_equal 1, firm.clients_using_sql.size
176
+ assert_equal 1, Firm.find_first.clients_using_sql.size
177
+ end
178
+
179
+ def test_find_all
180
+ assert_equal 2, Firm.find_first.clients.find_all("type = 'Client'").length
181
+ assert_equal 1, Firm.find_first.clients.find_all("name = 'Summit'").length
182
+ end
183
+
184
+ def test_find_in_collection
185
+ assert_equal Client.find(2).name, @signals37.clients.find(2).name
186
+ assert_equal Client.find(2).name, @signals37.clients.find {|c| c.name == @signals37.clients.find(2).name }.name
187
+ assert_raises(ActiveRecord::RecordNotFound) { @signals37.clients.find(6) }
188
+ end
189
+
190
+ def test_adding
191
+ force_signal37_to_load_all_clients_of_firm
192
+ natural = Client.new("name" => "Natural Company")
193
+ @signals37.clients_of_firm << natural
194
+ assert_equal 2, @signals37.clients_of_firm.size # checking via the collection
195
+ assert_equal 2, @signals37.clients_of_firm(true).size # checking using the db
196
+ assert_equal natural, @signals37.clients_of_firm.last
197
+ end
198
+
199
+ def test_adding_a_mismatch_class
200
+ assert_raises(ActiveRecord::AssociationTypeMismatch) { @signals37.clients_of_firm << nil }
201
+ assert_raises(ActiveRecord::AssociationTypeMismatch) { @signals37.clients_of_firm << 1 }
202
+ assert_raises(ActiveRecord::AssociationTypeMismatch) { @signals37.clients_of_firm << Topic.find(1) }
203
+ end
204
+
205
+ def test_adding_a_collection
206
+ force_signal37_to_load_all_clients_of_firm
207
+ @signals37.clients_of_firm.concat(Client.new("name" => "Natural Company"), Client.new("name" => "Apple"))
208
+ assert_equal 3, @signals37.clients_of_firm.size
209
+ assert_equal 3, @signals37.clients_of_firm(true).size
210
+ end
211
+
212
+ def test_build
213
+ new_client = @signals37.clients_of_firm.build("name" => "Another Client")
214
+ assert_equal "Another Client", new_client.name
215
+ assert new_client.save
216
+ assert_equal 2, @signals37.clients_of_firm(true).size
217
+ end
218
+
219
+ def test_create
220
+ force_signal37_to_load_all_clients_of_firm
221
+ new_client = @signals37.clients_of_firm.create("name" => "Another Client")
222
+ assert_equal new_client, @signals37.clients_of_firm.last
223
+ assert_equal new_client, @signals37.clients_of_firm(true).last
224
+ end
225
+
226
+ def test_deleting
227
+ force_signal37_to_load_all_clients_of_firm
228
+ @signals37.clients_of_firm.delete(@signals37.clients_of_firm.first)
229
+ assert_equal 0, @signals37.clients_of_firm.size
230
+ assert_equal 0, @signals37.clients_of_firm(true).size
231
+ end
232
+
233
+ def test_deleting_a_collection
234
+ force_signal37_to_load_all_clients_of_firm
235
+ @signals37.clients_of_firm.create("name" => "Another Client")
236
+ assert_equal 2, @signals37.clients_of_firm.size
237
+ @signals37.clients_of_firm.delete([@signals37.clients_of_firm[0], @signals37.clients_of_firm[1]])
238
+ assert_equal 0, @signals37.clients_of_firm.size
239
+ assert_equal 0, @signals37.clients_of_firm(true).size
240
+ end
241
+
242
+ def test_deleting_a_association_collection
243
+ force_signal37_to_load_all_clients_of_firm
244
+ @signals37.clients_of_firm.create("name" => "Another Client")
245
+ assert_equal 2, @signals37.clients_of_firm.size
246
+ @signals37.clients_of_firm.delete(@signals37.clients_of_firm)
247
+ assert_equal 0, @signals37.clients_of_firm.size
248
+ assert_equal 0, @signals37.clients_of_firm(true).size
249
+ end
250
+
251
+ def test_deleting_a_item_which_is_not_in_the_collection
252
+ force_signal37_to_load_all_clients_of_firm
253
+ summit = Client.find_first("name = 'Summit'")
254
+ @signals37.clients_of_firm.delete(summit)
255
+ assert_equal 1, @signals37.clients_of_firm.size
256
+ assert_equal 1, @signals37.clients_of_firm(true).size
257
+ assert_equal 2, summit.client_of
258
+ end
259
+
260
+ def test_destroy_all
261
+ force_signal37_to_load_all_clients_of_firm
262
+ assert !@signals37.clients_of_firm.empty?
263
+ @signals37.clients_of_firm.destroy_all
264
+ assert @signals37.clients_of_firm.empty?
265
+ assert @signals37.clients_of_firm(true).empty?
266
+ end
267
+
268
+ def test_dependence
269
+ assert_equal 2, Client.find_all.length
270
+ Firm.find_first.destroy
271
+ assert_equal 0, Client.find_all.length
272
+ end
273
+
274
+ def test_dependence_with_transaction_support_on_failure
275
+ assert_equal 2, Client.find_all.length
276
+ firm = Firm.find_first
277
+ clients = firm.clients
278
+ clients.last.instance_eval { def before_destroy() raise "Trigger rollback" end }
279
+
280
+ firm.destroy rescue "do nothing"
281
+
282
+ assert_equal 2, Client.find_all.length
283
+ end
284
+
285
+ def test_dependence_on_account
286
+ assert_equal 2, Account.find_all.length
287
+ @signals37.destroy
288
+ assert_equal 1, Account.find_all.length
289
+ end
290
+
291
+ def test_included_in_collection
292
+ assert @signals37.clients.include?(Client.find(2))
293
+ end
294
+ end
295
+
296
+ class BelongsToAssociationsTest < Test::Unit::TestCase
297
+ def setup
298
+ create_fixtures "accounts", "companies", "accounts", "developers", "projects", "developers_projects", "topics"
299
+ @signals37 = Firm.find(1)
300
+ end
301
+
302
+ def test_belongs_to
303
+ Client.find(3).firm.name
304
+ assert_equal @signals37.name, Client.find(3).firm.name
305
+ assert !Client.find(3).firm.nil?, "Microsoft should have a firm"
306
+ end
307
+
308
+ def test_type_mismatch
309
+ assert_raises(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = 1 }
310
+ assert_raises(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = Project.find(1) }
311
+ end
312
+
313
+ def test_natural_assignment
314
+ apple = Firm.create("name" => "Apple")
315
+ citibank = Account.create("credit_limit" => 10)
316
+ citibank.firm = apple
317
+ assert_equal apple.id, citibank.firm_id
318
+ end
319
+
320
+ def test_natural_assignment_to_nil
321
+ client = Client.find(3)
322
+ client.firm = nil
323
+ client.save
324
+ assert_nil client.firm(true)
325
+ assert_nil client.client_of
326
+ end
327
+
328
+ def test_with_different_class_name
329
+ assert_equal Company.find(1).name, Company.find(3).firm_with_other_name.name
330
+ assert !Company.find(3).firm_with_other_name.empty?, "Microsoft should have a firm"
331
+ end
332
+
333
+ def test_with_condition
334
+ assert_equal Company.find(1).name, Company.find(3).firm_with_condition.name
335
+ assert !Company.find(3).firm_with_condition.empty?, "Microsoft should have a firm"
336
+ end
337
+
338
+ def test_belongs_to_counter
339
+ debate = Topic.create("title" => "debate")
340
+ assert_equal 0, debate.send(:read_attribute, "replies_count"), "No replies yet"
341
+
342
+ trash = debate.replies.create("title" => "blah!", "content" => "world around!")
343
+ assert_equal 1, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply created"
344
+
345
+ trash.destroy
346
+ assert_equal 0, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply deleted"
347
+ end
348
+
349
+ def xtest_counter_cache
350
+ apple = Firm.create("name" => "Apple")
351
+ final_cut = apple.clients.create("name" => "Final Cut")
352
+
353
+ apple.clients.to_s
354
+ assert_equal 1, apple.clients.size, "Created one client"
355
+
356
+ apple.companies_count = 2
357
+ apple.save
358
+
359
+ apple = Firm.find_first("name = 'Apple'")
360
+ assert_equal 2, apple.clients.size, "Should use the new cached number"
361
+
362
+ apple.clients.to_s
363
+ assert_equal 1, apple.clients.size, "Should not use the cached number, but go to the database"
364
+ end
365
+ end
366
+
367
+
368
+ class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase
369
+ def setup
370
+ create_fixtures "accounts"
371
+ create_fixtures "companies"
372
+ create_fixtures "accounts"
373
+ create_fixtures "developers"
374
+ create_fixtures "projects"
375
+ create_fixtures "developers_projects"
376
+ @signals37 = Firm.find(1)
377
+ end
378
+
379
+ def test_has_and_belongs_to_many
380
+ david = Developer.find(1)
381
+
382
+ assert !david.projects.empty?
383
+ assert_equal 2, david.projects.size
384
+
385
+ active_record = Project.find(1)
386
+ assert !active_record.developers.empty?
387
+ assert_equal 2, active_record.developers.size
388
+ assert_equal david.name, active_record.developers.first.name
389
+ end
390
+
391
+ def test_addings
392
+ jamis = Developer.find(2)
393
+ jamis.projects.id # causing the collection to load
394
+ action_controller = Project.find(2)
395
+ assert_equal 1, jamis.projects.size
396
+ assert_equal 1, action_controller.developers.size
397
+
398
+ jamis.projects << action_controller
399
+
400
+ assert_equal 2, jamis.projects.size
401
+ assert_equal 2, jamis.projects(true).size
402
+ assert_equal 2, action_controller.developers(true).size
403
+ end
404
+
405
+ def test_adding_type_mismatch
406
+ jamis = Developer.find(2)
407
+ assert_raises(ActiveRecord::AssociationTypeMismatch) { jamis.projects << nil }
408
+ assert_raises(ActiveRecord::AssociationTypeMismatch) { jamis.projects << 1 }
409
+ end
410
+
411
+ def test_adding_from_the_project
412
+ jamis = Developer.find(2)
413
+ action_controller = Project.find(2)
414
+ action_controller.developers.id
415
+ assert_equal 1, jamis.projects.size
416
+ assert_equal 1, action_controller.developers.size
417
+
418
+ action_controller.developers << jamis
419
+
420
+ assert_equal 2, jamis.projects(true).size
421
+ assert_equal 2, action_controller.developers.size
422
+ assert_equal 2, action_controller.developers(true).size
423
+ end
424
+
425
+ def test_adding_a_collection
426
+ aridridel = Developer.new("name" => "Aridridel")
427
+ aridridel.save
428
+ aridridel.projects.id
429
+ aridridel.projects.concat([ Project.find(1), Project.find(2) ])
430
+ assert_equal 2, aridridel.projects.size
431
+ assert_equal 2, aridridel.projects(true).size
432
+ end
433
+
434
+ def test_deleting
435
+ david = Developer.find(1)
436
+ active_record = Project.find(1)
437
+ david.projects.id
438
+ assert_equal 2, david.projects.size
439
+ assert_equal 2, active_record.developers.size
440
+
441
+ david.projects.delete(active_record)
442
+
443
+ assert_equal 1, david.projects.size
444
+ assert_equal 1, david.projects(true).size
445
+ assert_equal 1, active_record.developers(true).size
446
+ end
447
+
448
+ def test_deleting_a_collection
449
+ david = Developer.find(1)
450
+ david.projects.id
451
+ david.projects.delete(Project.find_all)
452
+ assert_equal 0, david.projects.size
453
+ assert_equal 0, david.projects(true).size
454
+ end
455
+
456
+ def test_deleting_a_association_collection
457
+ david = Developer.find(1)
458
+ david.projects.id
459
+ david.projects.delete(david.projects)
460
+ assert_equal 0, david.projects.size
461
+ assert_equal 0, david.projects(true).size
462
+ end
463
+
464
+ def test_removing_associations_on_destroy
465
+ Developer.find(1).destroy
466
+ assert Developer.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = '1'").empty?
467
+ end
468
+
469
+ def test_destroy_all
470
+ david = Developer.find(1)
471
+ david.projects.id
472
+ assert !david.projects.empty?
473
+ david.projects.destroy_all
474
+ assert david.projects.empty?
475
+ assert david.projects(true).empty?
476
+ end
477
+ end