odbc-rails 1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. data/AUTHORS +16 -0
  2. data/COPYING +21 -0
  3. data/ChangeLog +89 -0
  4. data/LICENSE +5 -0
  5. data/NEWS +12 -0
  6. data/README +282 -0
  7. data/lib/active_record/connection_adapters/odbc_adapter.rb +1792 -0
  8. data/lib/active_record/vendor/odbcext_db2.rb +87 -0
  9. data/lib/active_record/vendor/odbcext_informix.rb +132 -0
  10. data/lib/active_record/vendor/odbcext_informix_col.rb +45 -0
  11. data/lib/active_record/vendor/odbcext_ingres.rb +156 -0
  12. data/lib/active_record/vendor/odbcext_microsoftsqlserver.rb +185 -0
  13. data/lib/active_record/vendor/odbcext_microsoftsqlserver_col.rb +40 -0
  14. data/lib/active_record/vendor/odbcext_mysql.rb +136 -0
  15. data/lib/active_record/vendor/odbcext_oracle.rb +220 -0
  16. data/lib/active_record/vendor/odbcext_postgresql.rb +179 -0
  17. data/lib/active_record/vendor/odbcext_progress.rb +139 -0
  18. data/lib/active_record/vendor/odbcext_progress89.rb +259 -0
  19. data/lib/active_record/vendor/odbcext_sybase.rb +212 -0
  20. data/lib/active_record/vendor/odbcext_sybase_col.rb +49 -0
  21. data/lib/active_record/vendor/odbcext_virtuoso.rb +146 -0
  22. data/lib/odbc_adapter.rb +28 -0
  23. data/support/lib/active_record/connection_adapters/abstract/schema_definitions.rb +259 -0
  24. data/support/odbc_rails.diff +707 -0
  25. data/support/pack_odbc.rb +119 -0
  26. data/support/rake/rails_plugin_package_task.rb +212 -0
  27. data/support/test/base_test.rb +1349 -0
  28. data/support/test/migration_test.rb +566 -0
  29. data/test/connections/native_odbc/connection.rb +95 -0
  30. data/test/fixtures/db_definitions/db2.drop.sql +30 -0
  31. data/test/fixtures/db_definitions/db2.sql +217 -0
  32. data/test/fixtures/db_definitions/db22.drop.sql +2 -0
  33. data/test/fixtures/db_definitions/db22.sql +5 -0
  34. data/test/fixtures/db_definitions/informix.drop.sql +30 -0
  35. data/test/fixtures/db_definitions/informix.sql +205 -0
  36. data/test/fixtures/db_definitions/informix2.drop.sql +2 -0
  37. data/test/fixtures/db_definitions/informix2.sql +5 -0
  38. data/test/fixtures/db_definitions/ingres.drop.sql +62 -0
  39. data/test/fixtures/db_definitions/ingres.sql +232 -0
  40. data/test/fixtures/db_definitions/ingres2.drop.sql +2 -0
  41. data/test/fixtures/db_definitions/ingres2.sql +5 -0
  42. data/test/fixtures/db_definitions/mysql.drop.sql +30 -0
  43. data/test/fixtures/db_definitions/mysql.sql +219 -0
  44. data/test/fixtures/db_definitions/mysql2.drop.sql +2 -0
  45. data/test/fixtures/db_definitions/mysql2.sql +5 -0
  46. data/test/fixtures/db_definitions/oracle_odbc.drop.sql +64 -0
  47. data/test/fixtures/db_definitions/oracle_odbc.sql +257 -0
  48. data/test/fixtures/db_definitions/oracle_odbc2.drop.sql +2 -0
  49. data/test/fixtures/db_definitions/oracle_odbc2.sql +6 -0
  50. data/test/fixtures/db_definitions/progress.drop.sql +61 -0
  51. data/test/fixtures/db_definitions/progress.sql +234 -0
  52. data/test/fixtures/db_definitions/progress2.drop.sql +2 -0
  53. data/test/fixtures/db_definitions/progress2.sql +6 -0
  54. data/test/fixtures/db_definitions/sqlserver.drop.sql +30 -0
  55. data/test/fixtures/db_definitions/sqlserver.sql +203 -0
  56. data/test/fixtures/db_definitions/sqlserver2.drop.sql +2 -0
  57. data/test/fixtures/db_definitions/sqlserver2.sql +5 -0
  58. data/test/fixtures/db_definitions/sybase.drop.sql +31 -0
  59. data/test/fixtures/db_definitions/sybase.sql +204 -0
  60. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  61. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  62. data/test/fixtures/db_definitions/virtuoso.drop.sql +30 -0
  63. data/test/fixtures/db_definitions/virtuoso.sql +200 -0
  64. data/test/fixtures/db_definitions/virtuoso2.drop.sql +2 -0
  65. data/test/fixtures/db_definitions/virtuoso2.sql +5 -0
  66. metadata +149 -0
@@ -0,0 +1,119 @@
1
+ #
2
+ # $Id: pack_odbc.rb,v 1.2 2006/08/21 23:37:17 source Exp $
3
+ #
4
+ # OpenLink ODBC Adapter for Ruby on Rails
5
+ # Copyright (C) 2006 OpenLink Software
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject
13
+ # to the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
22
+ # ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
23
+ # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+ #
26
+
27
+ #
28
+ # Copies Rails ODBC adapter files from development ActiveRecord tree to a
29
+ # staging area ready for packaging.
30
+ #
31
+
32
+ require 'fileutils'
33
+ require 'find'
34
+ require 'ftools'
35
+ require 'rdoc/rdoc'
36
+
37
+ # Development ActiveRecord source tree
38
+ $AR_DEV_ROOT = "/data/radrails/carl/activerecord_svn_co"
39
+ # Staging area
40
+ $PACK_ROOT = "/tmp/rails_odbc_pack"
41
+ # Location of miscellaneous files not in $AR_DEV_ROOT
42
+ $MISC_ROOT = "/dev/rails_odbc"
43
+
44
+ miscFiles = [
45
+ "install_odbc.rb",
46
+ "pack_odbc.rb",
47
+ "readme.html"
48
+ ]
49
+
50
+ # Stems of new .sql files added to $AR_DEV_ROOT/test/fixtures/db_definitions
51
+ # !! UPDATE AS SUPPORT FOR OTHER DATABASES IS ADDED TO ODBC ADAPTER
52
+ wantedBasenamePrefixes = [
53
+ "informix", "ingres", "virtuoso", "oracle_odbc"
54
+ ]
55
+
56
+ # Files in public ActiveRecord source tree which have been modified for
57
+ # ODBC adapter
58
+ modifiedFiles = [
59
+ "./test/base_test.rb",
60
+ "./test/migration_test.rb",
61
+ "./lib/active_record/connection_adapters/abstract/schema_definitions.rb"
62
+ ]
63
+
64
+ raise Exception, "Directory doesn't exist: #{$AR_DEV_ROOT}" if !File.exist?($AR_DEV_ROOT)
65
+
66
+ # Create directory tree under $PACK_ROOT
67
+ FileUtils.mkdir_p($PACK_ROOT)
68
+ FileUtils.mkdir_p(File.join($PACK_ROOT, 'new_files/lib/active_record/connection_adapters'))
69
+ FileUtils.mkdir_p(File.join($PACK_ROOT, 'new_files/lib/active_record/vendor'))
70
+ FileUtils.mkdir_p(File.join($PACK_ROOT, 'new_files/test/fixtures/db_definitions'))
71
+ FileUtils.mkdir_p(File.join($PACK_ROOT, 'new_files/test/connections/native_odbc'))
72
+ FileUtils.mkdir_p(File.join($PACK_ROOT,
73
+ 'modified_files/lib/active_record/connection_adapters/abstract'))
74
+ FileUtils.mkdir_p(File.join($PACK_ROOT, 'modified_files/test'))
75
+
76
+ # Generate RDoc's
77
+ Dir.glob($AR_DEV_ROOT + "/**/odbc_adapter.rb") { |f|
78
+ FileUtils.cp(f, $PACK_ROOT)
79
+ Dir.chdir($PACK_ROOT)
80
+ FileUtils.rmtree('doc')
81
+ r = RDoc::RDoc.new
82
+ r.document(['-q', 'odbc_adapter.rb'])
83
+ FileUtils.rm('odbc_adapter.rb')
84
+ }
85
+
86
+ # Copy new files into $PACK_ROOT
87
+ # i.e. files not in the current ActiveRecord distribution
88
+ Dir.chdir($AR_DEV_ROOT)
89
+ Find.find(".") { |f|
90
+ if File.fnmatch("./**/odbc*.rb" , f)
91
+ FileUtils.cp(f, File.join($PACK_ROOT, "new_files", *f.split(/\//)),
92
+ :verbose => true)
93
+ end
94
+ }
95
+
96
+ f = "./test/connections/native_odbc/connection.rb"
97
+ FileUtils.cp(f, File.join($PACK_ROOT, "new_files", *f.split(/\//)),
98
+ :verbose => true)
99
+
100
+ Find.find(".") { |f|
101
+ if File.fnmatch("./**/*.sql" , f)
102
+ basename = File.basename(f, ".sql")
103
+ wantedBasenamePrefixes.each do |prefix|
104
+ if basename.match("^#{prefix}")
105
+ FileUtils.cp(f, File.join($PACK_ROOT, "new_files", *f.split(/\//)),
106
+ :verbose => true)
107
+ end
108
+ end
109
+ end
110
+ }
111
+
112
+ modifiedFiles.each do |f|
113
+ FileUtils.cp(f, File.join($PACK_ROOT, "modified_files", *f.split(/\//)),
114
+ :verbose => true)
115
+ end
116
+
117
+ miscFiles.each do |f|
118
+ FileUtils.cp(File.join($MISC_ROOT, f), $PACK_ROOT, :verbose => true)
119
+ end
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright (c) 2006 by Zak Mandhro
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software
6
+ # and associated documentation files (the "Software"), to deal in the Software without restriction,
7
+ # including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
8
+ # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ # portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15
+ # LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
16
+ # EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
18
+ # USE OR OTHER DEALINGS IN THE SOFTWARE.
19
+ #
20
+ # = Rails Plug-in Package Task
21
+ #
22
+ # RailsPluginPackageTask is a rake task designed to automate the publishing of
23
+ # Ruby on Rails plug-ins. The Rails plug-in installer _RecursiveHTTPFetcher_ makes
24
+ # certain assumptions about the web servers that does not hold through from
25
+ # server to server, for example:
26
+ #
27
+ # * Server generates an index page with links
28
+ # * All links to plug-in files are relative links
29
+ # * Folder links end with a forward slash (used to recurse)
30
+ #
31
+ # RubyForge web server is an example of where these assupmtions don't hold
32
+ # true. As a result, you can not simply copy your files to a web server
33
+ # and expect Rails HTTP plugin installer to just work.
34
+ #
35
+ # This Rake task helps fill the gap by complying to the plug-in scripts assumptions.
36
+ # Following the Rake package task conventions, it defines the "rails_plugin" task
37
+ # that recurses through your _package_files_, generates compliant index.html for
38
+ # each folder (that contains a file), and creates a directory structure that you
39
+ # can publish as a set for your plugin.
40
+ #
41
+ # = Example
42
+ #
43
+ # The following example uses the Rake::RailsPluginPackageTask to create
44
+ # the package. It then uses the Rake::SshDirPublisher to publish the plugin
45
+ # directory to RubyForge.
46
+ # #
47
+ # Rake::RailsPluginPackageTask.new(ProjectInfo[:name], ProjectInfo[:version]) do |p|
48
+ # p.package_files = PluginPackageFiles
49
+ # p.plugin_files = FileList["rails_plugin/**/*"]
50
+ # p.extra_links = {"Project page"=>ProjectInfo[:homepage],
51
+ # "Author: #{ProjectInfo[:author_name]}"=>ProjectInfo[:author_link]}
52
+ # p.verbose = true
53
+ # end
54
+ # task :rails_plugin=>:clobber
55
+ #
56
+ # desc "Publish Ruby on Rails plug-in on RubyForge"
57
+ # task :release_plugin=>:rails_plugin do |task|
58
+ # pub = Rake::SshDirPublisher.new("#{RubyForgeConfig[:user_name]}@rubyforge.org",
59
+ # "/var/www/gforge-projects/#{RubyForgeConfig[:unix_name]}",
60
+ # "pkg/rails_plugin")
61
+ # pub.upload()
62
+ # end
63
+
64
+
65
+ require 'rake'
66
+ require 'rake/tasklib'
67
+
68
+ module Rake
69
+ # RailsPluginPackageTask defines the "rails_plugin" task
70
+ # that recurses through your _package_files_, generates compliant index.html for
71
+ # each folder (that contains a file), and creates a directory structure that you
72
+ # can publish as a set for your plugin.
73
+ #
74
+ # Noteworthy attributes:
75
+ #
76
+ # [package_dir] Directory to store the package. Default 'pkg/rails_plugin'
77
+ #
78
+ # [package_dir] Files to include in the plugin.
79
+ #
80
+ # [extra_links] Links to put on every generated index page. Can be a hash, e.g.
81
+ # {"Home"=>"http://roxml.rubyforge.org"}, an array of strings or
82
+ # a single string.
83
+ #
84
+ # [plugin_files] Files to be placed in the root folder of the plug-in, e.g.
85
+ # init.rb. All files that are in the root of _package_dir_
86
+ # will also be placed in the root of the plug-in.
87
+ #
88
+ class RailsPluginPackageTask < TaskLib
89
+ # Name of plug-in or application
90
+ attr_accessor :name
91
+ # Version of plugin - distribution folder will be name_version
92
+ attr_accessor :version
93
+ # Directory used to store the package files (default is 'pkg/rails_plugin').
94
+ attr_accessor :package_dir
95
+ # Files to be stored in the package
96
+ attr_accessor :package_files
97
+ # Files to go into the root of the plug-in folder (e.g. init.rb)
98
+ attr_accessor :plugin_files
99
+ # Homepage for more information
100
+ attr_accessor :extra_links
101
+ # Verbose [true | false]
102
+ attr_accessor :verbose
103
+
104
+ # Create the "rails_plugin" task
105
+ def initialize(name=nil, version=nil)
106
+ init(name, version)
107
+ yield self if block_given?
108
+ define unless name.nil?
109
+ end
110
+
111
+ # Initialize with defaults
112
+ def init(name, version)
113
+ @name = name
114
+ @version = version
115
+ @extra_links = nil
116
+ @package_files = Rake::FileList.new
117
+ @plugin_files = Rake::FileList.new
118
+ @package_dir = 'pkg/rails_plugin'
119
+ @folders = {}
120
+ @verbose = false
121
+ end
122
+
123
+ # Define the rails_plugin task
124
+ def define
125
+ desc "Create Rails plug-in package"
126
+ task :rails_plugin do
127
+ @dest = "#@package_dir/#{@name}_#{@version}"
128
+ makedirs(@dest,:verbose=>false)
129
+ @plugin_files.each do |fn|
130
+ cp(fn, @dest,:verbose=>false)
131
+ add_file(File.basename(fn))
132
+ end
133
+
134
+ @package_files.each do |fn|
135
+ puts ". #{fn}" if verbose
136
+ f = File.join(@dest, fn)
137
+ fdir = File.dirname(f)
138
+ unless File.exist?(fdir)
139
+ mkdir_p(fdir,:verbose=>false)
140
+ add_folder("#{fdir}/")
141
+ end
142
+ if File.directory?(fn)
143
+ mkdir_p(f,:verbose=>false)
144
+ add_folder("#{fn}/")
145
+ else
146
+ cp(fn, f, :verbose=>false)
147
+ add_file(fn)
148
+ end
149
+ end
150
+
151
+ generate_index_files()
152
+ end
153
+ end
154
+
155
+ # Generate the index.html files
156
+ def generate_index_files
157
+ @folders.each do |folder, files|
158
+ puts " + Creating #{@dest}/#{folder}/index.html" if @verbose
159
+ File.open("#{@dest}/#{folder}/index.html", "w") do |index|
160
+ title = "Rails Plug-in for #@name #@version"
161
+ index.write("<html><head><title>#{title}</title></head>\n")
162
+ index.write("<body>\n")
163
+ index.write("<h2>#{title}</h2>\n")
164
+ extra_links = create_extra_links()
165
+ index.write("<p>#{extra_links}</p>\n") if extra_links
166
+ files.each { |fn|
167
+ puts(" - Adding #{fn}") if @verbose
168
+ index.write("&nbsp;&nbsp;<a href=\"#{fn}\">#{fn}</a><br/>\n")
169
+ }
170
+ index.write("<hr size=\"1\"/><p style=\"font-size: x-small\">Generated with RailsPluginPackageTask<p>")
171
+ index.write("</body>\n")
172
+ index.write("</html>\n")
173
+ end
174
+ end
175
+ end
176
+
177
+ private
178
+ # Add a file to the folders hash
179
+ def add_file(filename)
180
+ dir = File.dirname(filename).gsub("#{@dest}",".")
181
+ fn = File.basename(filename)
182
+ folder = @folders[dir] || @folders[dir]=[]
183
+ folder << fn
184
+ end
185
+
186
+ # Add a folder to the folders hash
187
+ def add_folder(folder_name)
188
+ dir = File.dirname(folder_name).gsub("#{@dest}",".").gsub("./","")
189
+ fn = File.basename(folder_name) + "/"
190
+ folder = @folders[dir] || @folders[dir]=[]
191
+ folder << fn
192
+ end
193
+
194
+ # Create the anchor tag for extra links
195
+ def create_extra_links
196
+ return nil unless @extra_links
197
+ x_links = ""
198
+ if (@extra_links.class==Hash)
199
+ @extra_links.each do |k,v|
200
+ x_links << "<a href=\"#{v}\">#{k}</a>&nbsp;"
201
+ end
202
+ elsif (@extra_links.class==Array)
203
+ @extra_links.each do |link|
204
+ x_links << "<a href=\"#{link}\">#{link}</a>&nbsp;"
205
+ end
206
+ else
207
+ x_links = "<a href=\"#{@extra_links.to_s}\">#{@extra_links.to_s}</a>"
208
+ end
209
+ return x_links
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,1349 @@
1
+ require 'abstract_unit'
2
+ require 'fixtures/topic'
3
+ require 'fixtures/reply'
4
+ require 'fixtures/company'
5
+ require 'fixtures/customer'
6
+ require 'fixtures/developer'
7
+ require 'fixtures/project'
8
+ require 'fixtures/default'
9
+ require 'fixtures/auto_id'
10
+ require 'fixtures/column_name'
11
+ require 'fixtures/subscriber'
12
+ require 'fixtures/keyboard'
13
+
14
+ class Category < ActiveRecord::Base; end
15
+ class Smarts < ActiveRecord::Base; end
16
+ class CreditCard < ActiveRecord::Base; end
17
+ class MasterCreditCard < ActiveRecord::Base; end
18
+ class Post < ActiveRecord::Base; end
19
+ class Computer < ActiveRecord::Base; end
20
+ class NonExistentTable < ActiveRecord::Base; end
21
+ class TestOracleDefault < ActiveRecord::Base; end
22
+
23
+ class LoosePerson < ActiveRecord::Base
24
+ attr_protected :credit_rating, :administrator
25
+ self.abstract_class = true
26
+ end
27
+
28
+ class LooseDescendant < LoosePerson
29
+ attr_protected :phone_number
30
+ end
31
+
32
+ class TightPerson < ActiveRecord::Base
33
+ attr_accessible :name, :address
34
+ end
35
+
36
+ class TightDescendant < TightPerson
37
+ attr_accessible :phone_number
38
+ end
39
+
40
+ class Booleantest < ActiveRecord::Base; end
41
+
42
+ class Task < ActiveRecord::Base
43
+ attr_protected :starting
44
+ end
45
+
46
+ class BasicsTest < Test::Unit::TestCase
47
+ fixtures :topics, :companies, :developers, :projects, :computers, :accounts
48
+
49
+ def test_table_exists
50
+ assert !NonExistentTable.table_exists?
51
+ assert Topic.table_exists?
52
+ end
53
+
54
+ def test_set_attributes
55
+ topic = Topic.find(1)
56
+ topic.attributes = { "title" => "Budget", "author_name" => "Jason" }
57
+ topic.save
58
+ assert_equal("Budget", topic.title)
59
+ assert_equal("Jason", topic.author_name)
60
+ assert_equal(topics(:first).author_email_address, Topic.find(1).author_email_address)
61
+ end
62
+
63
+ def test_integers_as_nil
64
+ test = AutoId.create('value' => '')
65
+ assert_nil AutoId.find(test.id).value
66
+ end
67
+
68
+ def test_set_attributes_with_block
69
+ topic = Topic.new do |t|
70
+ t.title = "Budget"
71
+ t.author_name = "Jason"
72
+ end
73
+
74
+ assert_equal("Budget", topic.title)
75
+ assert_equal("Jason", topic.author_name)
76
+ end
77
+
78
+ def test_respond_to?
79
+ topic = Topic.find(1)
80
+ assert topic.respond_to?("title")
81
+ assert topic.respond_to?("title?")
82
+ assert topic.respond_to?("title=")
83
+ assert topic.respond_to?(:title)
84
+ assert topic.respond_to?(:title?)
85
+ assert topic.respond_to?(:title=)
86
+ assert topic.respond_to?("author_name")
87
+ assert topic.respond_to?("attribute_names")
88
+ assert !topic.respond_to?("nothingness")
89
+ assert !topic.respond_to?(:nothingness)
90
+ end
91
+
92
+ def test_array_content
93
+ topic = Topic.new
94
+ topic.content = %w( one two three )
95
+ topic.save
96
+
97
+ assert_equal(%w( one two three ), Topic.find(topic.id).content)
98
+ end
99
+
100
+ def test_hash_content
101
+ topic = Topic.new
102
+ topic.content = { "one" => 1, "two" => 2 }
103
+ topic.save
104
+
105
+ assert_equal 2, Topic.find(topic.id).content["two"]
106
+
107
+ topic.content["three"] = 3
108
+ topic.save
109
+
110
+ assert_equal 3, Topic.find(topic.id).content["three"]
111
+ end
112
+
113
+ def test_update_array_content
114
+ topic = Topic.new
115
+ topic.content = %w( one two three )
116
+
117
+ topic.content.push "four"
118
+ assert_equal(%w( one two three four ), topic.content)
119
+
120
+ topic.save
121
+
122
+ topic = Topic.find(topic.id)
123
+ topic.content << "five"
124
+ assert_equal(%w( one two three four five ), topic.content)
125
+ end
126
+
127
+ def test_case_sensitive_attributes_hash
128
+ # DB2 is not case-sensitive
129
+ return true if current_adapter?(:DB2Adapter)
130
+
131
+ assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.find(:first).attributes
132
+ end
133
+
134
+ def test_create
135
+ topic = Topic.new
136
+ topic.title = "New Topic"
137
+ topic.save
138
+ topic_reloaded = Topic.find(topic.id)
139
+ assert_equal("New Topic", topic_reloaded.title)
140
+ end
141
+
142
+ def test_save!
143
+ topic = Topic.new(:title => "New Topic")
144
+ assert topic.save!
145
+ end
146
+
147
+ def test_hashes_not_mangled
148
+ new_topic = { :title => "New Topic" }
149
+ new_topic_values = { :title => "AnotherTopic" }
150
+
151
+ topic = Topic.new(new_topic)
152
+ assert_equal new_topic[:title], topic.title
153
+
154
+ topic.attributes= new_topic_values
155
+ assert_equal new_topic_values[:title], topic.title
156
+ end
157
+
158
+ def test_create_many
159
+ topics = Topic.create([ { "title" => "first" }, { "title" => "second" }])
160
+ assert_equal 2, topics.size
161
+ assert_equal "first", topics.first.title
162
+ end
163
+
164
+ def test_create_columns_not_equal_attributes
165
+ topic = Topic.new
166
+ topic.title = 'Another New Topic'
167
+ topic.send :write_attribute, 'does_not_exist', 'test'
168
+ assert_nothing_raised { topic.save }
169
+ end
170
+
171
+ def test_create_through_factory
172
+ topic = Topic.create("title" => "New Topic")
173
+ topicReloaded = Topic.find(topic.id)
174
+ assert_equal(topic, topicReloaded)
175
+ end
176
+
177
+ def test_update
178
+ topic = Topic.new
179
+ topic.title = "Another New Topic"
180
+ topic.written_on = "2003-12-12 23:23:00"
181
+ topic.save
182
+ topicReloaded = Topic.find(topic.id)
183
+ assert_equal("Another New Topic", topicReloaded.title)
184
+
185
+ topicReloaded.title = "Updated topic"
186
+ topicReloaded.save
187
+
188
+ topicReloadedAgain = Topic.find(topic.id)
189
+
190
+ assert_equal("Updated topic", topicReloadedAgain.title)
191
+ end
192
+
193
+ def test_update_columns_not_equal_attributes
194
+ topic = Topic.new
195
+ topic.title = "Still another topic"
196
+ topic.save
197
+
198
+ topicReloaded = Topic.find(topic.id)
199
+ topicReloaded.title = "A New Topic"
200
+ topicReloaded.send :write_attribute, 'does_not_exist', 'test'
201
+ assert_nothing_raised { topicReloaded.save }
202
+ end
203
+
204
+ def test_write_attribute
205
+ topic = Topic.new
206
+ topic.send(:write_attribute, :title, "Still another topic")
207
+ assert_equal "Still another topic", topic.title
208
+
209
+ topic.send(:write_attribute, "title", "Still another topic: part 2")
210
+ assert_equal "Still another topic: part 2", topic.title
211
+ end
212
+
213
+ def test_read_attribute
214
+ topic = Topic.new
215
+ topic.title = "Don't change the topic"
216
+ assert_equal "Don't change the topic", topic.send(:read_attribute, "title")
217
+ assert_equal "Don't change the topic", topic["title"]
218
+
219
+ assert_equal "Don't change the topic", topic.send(:read_attribute, :title)
220
+ assert_equal "Don't change the topic", topic[:title]
221
+ end
222
+
223
+ def test_read_attribute_when_false
224
+ topic = topics(:first)
225
+ topic.approved = false
226
+ assert !topic.approved?, "approved should be false"
227
+ topic.approved = "false"
228
+ assert !topic.approved?, "approved should be false"
229
+ end
230
+
231
+ def test_read_attribute_when_true
232
+ topic = topics(:first)
233
+ topic.approved = true
234
+ assert topic.approved?, "approved should be true"
235
+ topic.approved = "true"
236
+ assert topic.approved?, "approved should be true"
237
+ end
238
+
239
+ def test_read_write_boolean_attribute
240
+ topic = Topic.new
241
+ # puts ""
242
+ # puts "New Topic"
243
+ # puts topic.inspect
244
+ topic.approved = "false"
245
+ # puts "Expecting false"
246
+ # puts topic.inspect
247
+ assert !topic.approved?, "approved should be false"
248
+ topic.approved = "false"
249
+ # puts "Expecting false"
250
+ # puts topic.inspect
251
+ assert !topic.approved?, "approved should be false"
252
+ topic.approved = "true"
253
+ # puts "Expecting true"
254
+ # puts topic.inspect
255
+ assert topic.approved?, "approved should be true"
256
+ topic.approved = "true"
257
+ # puts "Expecting true"
258
+ # puts topic.inspect
259
+ assert topic.approved?, "approved should be true"
260
+ # puts ""
261
+ end
262
+
263
+ def test_reader_generation
264
+ Topic.find(:first).title
265
+ Firm.find(:first).name
266
+ Client.find(:first).name
267
+ if ActiveRecord::Base.generate_read_methods
268
+ assert_readers(Topic, %w(type replies_count))
269
+ assert_readers(Firm, %w(type))
270
+ assert_readers(Client, %w(type ruby_type rating?))
271
+ else
272
+ [Topic, Firm, Client].each {|klass| assert_equal klass.read_methods, {}}
273
+ end
274
+ end
275
+
276
+ def test_reader_for_invalid_column_names
277
+ # column names which aren't legal ruby ids
278
+ topic = Topic.find(:first)
279
+ topic.send(:define_read_method, "mumub-jumbo".to_sym, "mumub-jumbo", nil)
280
+ assert !Topic.read_methods.include?("mumub-jumbo")
281
+ end
282
+
283
+ def test_non_attribute_access_and_assignment
284
+ topic = Topic.new
285
+ assert !topic.respond_to?("mumbo")
286
+ assert_raises(NoMethodError) { topic.mumbo }
287
+ assert_raises(NoMethodError) { topic.mumbo = 5 }
288
+ end
289
+
290
+ def test_preserving_date_objects
291
+ # SQL Server doesn't have a separate column type just for dates, so all are returned as time
292
+ return true if current_adapter?(:SQLServerAdapter)
293
+
294
+ if current_adapter?(:SybaseAdapter)
295
+ # Sybase ctlib does not (yet?) support the date type; use datetime instead.
296
+ assert_kind_of(
297
+ Time, Topic.find(1).last_read,
298
+ "The last_read attribute should be of the Time class"
299
+ )
300
+ elsif current_adapter?(:ODBCAdapter) && [:ingres, :microsoftsqlserver, :oracle].include?(ActiveRecord::Base.connection.dbmsName)
301
+ # Above databases don't have a pure date type. (They have a datetime-like type).
302
+ assert_kind_of(
303
+ Time, Topic.find(1).last_read,
304
+ "The last_read attribute should be of the Time class"
305
+ )
306
+ else
307
+ # ODBCAdapter fails against SQL Server because topics.last_read is
308
+ # defined as a datetime column, which is returned as a Ruby Time object.
309
+ assert_kind_of(
310
+ Date, Topic.find(1).last_read,
311
+ "The last_read attribute should be of the Date class"
312
+ )
313
+ end
314
+ end
315
+
316
+ def test_preserving_time_objects
317
+ assert_kind_of(
318
+ Time, Topic.find(1).bonus_time,
319
+ "The bonus_time attribute should be of the Time class"
320
+ )
321
+
322
+ assert_kind_of(
323
+ Time, Topic.find(1).written_on,
324
+ "The written_on attribute should be of the Time class"
325
+ )
326
+ end
327
+
328
+ def test_destroy
329
+ topic = Topic.new
330
+ topic.title = "Yet Another New Topic"
331
+ topic.written_on = "2003-12-12 23:23:00"
332
+ topic.save
333
+ topic.destroy
334
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
335
+ end
336
+
337
+ def test_destroy_returns_self
338
+ topic = Topic.new("title" => "Yet Another Title")
339
+ assert topic.save
340
+ assert_equal topic, topic.destroy, "destroy did not return destroyed object"
341
+ end
342
+
343
+ def test_record_not_found_exception
344
+ assert_raises(ActiveRecord::RecordNotFound) { topicReloaded = Topic.find(99999) }
345
+ end
346
+
347
+ def test_initialize_with_attributes
348
+ topic = Topic.new({
349
+ "title" => "initialized from attributes", "written_on" => "2003-12-12 23:23"
350
+ })
351
+
352
+ assert_equal("initialized from attributes", topic.title)
353
+ end
354
+
355
+ def test_initialize_with_invalid_attribute
356
+ begin
357
+ topic = Topic.new({ "title" => "test",
358
+ "last_read(1i)" => "2005", "last_read(2i)" => "2", "last_read(3i)" => "31"})
359
+ rescue ActiveRecord::MultiparameterAssignmentErrors => ex
360
+ assert_equal(1, ex.errors.size)
361
+ assert_equal("last_read", ex.errors[0].attribute)
362
+ end
363
+ end
364
+
365
+ def test_load
366
+ topics = Topic.find(:all, :order => 'id')
367
+ assert_equal(2, topics.size)
368
+ assert_equal(topics(:first).title, topics.first.title)
369
+ end
370
+
371
+ def test_load_with_condition
372
+ topics = Topic.find(:all, :conditions => "author_name = 'Mary'")
373
+
374
+ assert_equal(1, topics.size)
375
+ assert_equal(topics(:second).title, topics.first.title)
376
+ end
377
+
378
+ def test_table_name_guesses
379
+ assert_equal "topics", Topic.table_name
380
+
381
+ assert_equal "categories", Category.table_name
382
+ assert_equal "smarts", Smarts.table_name
383
+ assert_equal "credit_cards", CreditCard.table_name
384
+ assert_equal "master_credit_cards", MasterCreditCard.table_name
385
+
386
+ ActiveRecord::Base.pluralize_table_names = false
387
+ [Category, Smarts, CreditCard, MasterCreditCard].each{|c| c.reset_table_name}
388
+ assert_equal "category", Category.table_name
389
+ assert_equal "smarts", Smarts.table_name
390
+ assert_equal "credit_card", CreditCard.table_name
391
+ assert_equal "master_credit_card", MasterCreditCard.table_name
392
+ ActiveRecord::Base.pluralize_table_names = true
393
+ [Category, Smarts, CreditCard, MasterCreditCard].each{|c| c.reset_table_name}
394
+
395
+ ActiveRecord::Base.table_name_prefix = "test_"
396
+ Category.reset_table_name
397
+ assert_equal "test_categories", Category.table_name
398
+ ActiveRecord::Base.table_name_suffix = "_test"
399
+ Category.reset_table_name
400
+ assert_equal "test_categories_test", Category.table_name
401
+ ActiveRecord::Base.table_name_prefix = ""
402
+ Category.reset_table_name
403
+ assert_equal "categories_test", Category.table_name
404
+ ActiveRecord::Base.table_name_suffix = ""
405
+ Category.reset_table_name
406
+ assert_equal "categories", Category.table_name
407
+
408
+ ActiveRecord::Base.pluralize_table_names = false
409
+ ActiveRecord::Base.table_name_prefix = "test_"
410
+ Category.reset_table_name
411
+ assert_equal "test_category", Category.table_name
412
+ ActiveRecord::Base.table_name_suffix = "_test"
413
+ Category.reset_table_name
414
+ assert_equal "test_category_test", Category.table_name
415
+ ActiveRecord::Base.table_name_prefix = ""
416
+ Category.reset_table_name
417
+ assert_equal "category_test", Category.table_name
418
+ ActiveRecord::Base.table_name_suffix = ""
419
+ Category.reset_table_name
420
+ assert_equal "category", Category.table_name
421
+ ActiveRecord::Base.pluralize_table_names = true
422
+ [Category, Smarts, CreditCard, MasterCreditCard].each{|c| c.reset_table_name}
423
+ end
424
+
425
+ def test_destroy_all
426
+ assert_equal 2, Topic.count
427
+
428
+ Topic.destroy_all "author_name = 'Mary'"
429
+ assert_equal 1, Topic.count
430
+ end
431
+
432
+ def test_destroy_many
433
+ assert_equal 3, Client.count
434
+ Client.destroy([2, 3])
435
+ assert_equal 1, Client.count
436
+ end
437
+
438
+ def test_delete_many
439
+ Topic.delete([1, 2])
440
+ assert_equal 0, Topic.count
441
+ end
442
+
443
+ def test_boolean_attributes
444
+ assert ! Topic.find(1).approved?
445
+ assert Topic.find(2).approved?
446
+ end
447
+
448
+ def test_increment_counter
449
+ Topic.increment_counter("replies_count", 1)
450
+ assert_equal 1, Topic.find(1).replies_count
451
+
452
+ Topic.increment_counter("replies_count", 1)
453
+ assert_equal 2, Topic.find(1).replies_count
454
+ end
455
+
456
+ def test_decrement_counter
457
+ Topic.decrement_counter("replies_count", 2)
458
+ assert_equal 1, Topic.find(2).replies_count
459
+
460
+ Topic.decrement_counter("replies_count", 2)
461
+ assert_equal 0, Topic.find(1).replies_count
462
+ end
463
+
464
+ def test_update_all
465
+ # The ADO library doesn't support the number of affected rows
466
+ return true if current_adapter?(:SQLServerAdapter)
467
+
468
+ assert_equal 2, Topic.update_all("content = 'bulk updated!'")
469
+ assert_equal "bulk updated!", Topic.find(1).content
470
+ assert_equal "bulk updated!", Topic.find(2).content
471
+ assert_equal 2, Topic.update_all(['content = ?', 'bulk updated again!'])
472
+ assert_equal "bulk updated again!", Topic.find(1).content
473
+ assert_equal "bulk updated again!", Topic.find(2).content
474
+ end
475
+
476
+ def test_update_many
477
+ topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
478
+ updated = Topic.update(topic_data.keys, topic_data.values)
479
+
480
+ assert_equal 2, updated.size
481
+ assert_equal "1 updated", Topic.find(1).content
482
+ assert_equal "2 updated", Topic.find(2).content
483
+ end
484
+
485
+ def test_delete_all
486
+ # The ADO library doesn't support the number of affected rows
487
+ return true if current_adapter?(:SQLServerAdapter)
488
+
489
+ assert_equal 2, Topic.delete_all
490
+ end
491
+
492
+ def test_update_by_condition
493
+ Topic.update_all "content = 'bulk updated!'", ["approved = ?", true]
494
+ assert_equal "Have a nice day", Topic.find(1).content
495
+ assert_equal "bulk updated!", Topic.find(2).content
496
+ end
497
+
498
+ def test_attribute_present
499
+ t = Topic.new
500
+ t.title = "hello there!"
501
+ t.written_on = Time.now
502
+ assert t.attribute_present?("title")
503
+ assert t.attribute_present?("written_on")
504
+ assert !t.attribute_present?("content")
505
+ end
506
+
507
+ def test_attribute_keys_on_new_instance
508
+ t = Topic.new
509
+ assert_equal nil, t.title, "The topics table has a title column, so it should be nil"
510
+ assert_raise(NoMethodError) { t.title2 }
511
+ end
512
+
513
+ def test_class_name
514
+ assert_equal "Firm", ActiveRecord::Base.class_name("firms")
515
+ assert_equal "Category", ActiveRecord::Base.class_name("categories")
516
+ assert_equal "AccountHolder", ActiveRecord::Base.class_name("account_holder")
517
+
518
+ ActiveRecord::Base.pluralize_table_names = false
519
+ assert_equal "Firms", ActiveRecord::Base.class_name( "firms" )
520
+ ActiveRecord::Base.pluralize_table_names = true
521
+
522
+ ActiveRecord::Base.table_name_prefix = "test_"
523
+ assert_equal "Firm", ActiveRecord::Base.class_name( "test_firms" )
524
+ ActiveRecord::Base.table_name_suffix = "_tests"
525
+ assert_equal "Firm", ActiveRecord::Base.class_name( "test_firms_tests" )
526
+ ActiveRecord::Base.table_name_prefix = ""
527
+ assert_equal "Firm", ActiveRecord::Base.class_name( "firms_tests" )
528
+ ActiveRecord::Base.table_name_suffix = ""
529
+ assert_equal "Firm", ActiveRecord::Base.class_name( "firms" )
530
+ end
531
+
532
+ def test_null_fields
533
+ assert_nil Topic.find(1).parent_id
534
+ assert_nil Topic.create("title" => "Hey you").parent_id
535
+ end
536
+
537
+ def test_default_values
538
+ topic = Topic.new
539
+ assert topic.approved?
540
+ assert_nil topic.written_on
541
+ assert_nil topic.bonus_time
542
+ assert_nil topic.last_read
543
+
544
+ topic.save
545
+
546
+ topic = Topic.find(topic.id)
547
+ assert topic.approved?
548
+ assert_nil topic.last_read
549
+
550
+ # Oracle has some funky default handling, so it requires a bit of
551
+ # extra testing. See ticket #2788.
552
+ if current_adapter?(:OracleAdapter)
553
+ test = TestOracleDefault.new
554
+ assert_equal "X", test.test_char
555
+ assert_equal "hello", test.test_string
556
+ assert_equal 3, test.test_int
557
+ end
558
+ end
559
+
560
+ def test_utc_as_time_zone
561
+ # Oracle and SQLServer do not have a TIME datatype.
562
+ return true if current_adapter?(:SQLServerAdapter) || current_adapter?(:OracleAdapter)
563
+
564
+ Topic.default_timezone = :utc
565
+ attributes = { "bonus_time" => "5:42:00AM" }
566
+ topic = Topic.find(1)
567
+ topic.attributes = attributes
568
+ assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time
569
+ Topic.default_timezone = :local
570
+ end
571
+
572
+ def test_default_values_on_empty_strings
573
+ topic = Topic.new
574
+ #Sybase does not allow nulls in boolean columns
575
+ if current_adapter?(:ODBCAdapter) && ActiveRecord::Base.connection.dbmsName == :sybase
576
+ topic.approved = false
577
+ else
578
+ topic.approved = nil
579
+ end
580
+ topic.last_read = nil
581
+
582
+ topic.save
583
+
584
+ topic = Topic.find(topic.id)
585
+ assert_nil topic.last_read
586
+
587
+ # Sybase adapter does not allow nulls in boolean columns
588
+ if current_adapter?(:SybaseAdapter) ||
589
+ current_adapter?(:ODBCAdapter) && ActiveRecord::Base.connection.dbmsName == :sybase
590
+ assert topic.approved == false
591
+ else
592
+ assert_nil topic.approved
593
+ end
594
+ end
595
+
596
+ def test_equality
597
+ assert_equal Topic.find(1), Topic.find(2).topic
598
+ end
599
+
600
+ def test_equality_of_new_records
601
+ assert_not_equal Topic.new, Topic.new
602
+ end
603
+
604
+ def test_hashing
605
+ assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ]
606
+ end
607
+
608
+ def test_destroy_new_record
609
+ client = Client.new
610
+ client.destroy
611
+ assert client.frozen?
612
+ end
613
+
614
+ def test_destroy_record_with_associations
615
+ client = Client.find(3)
616
+ client.destroy
617
+ assert client.frozen?
618
+ assert_kind_of Firm, client.firm
619
+ assert_raises(TypeError) { client.name = "something else" }
620
+ end
621
+
622
+ def test_update_attribute
623
+ assert !Topic.find(1).approved?
624
+ Topic.find(1).update_attribute("approved", true)
625
+ assert Topic.find(1).approved?
626
+
627
+ Topic.find(1).update_attribute(:approved, false)
628
+ assert !Topic.find(1).approved?
629
+ end
630
+
631
+ def test_mass_assignment_protection
632
+ firm = Firm.new
633
+ firm.attributes = { "name" => "Next Angle", "rating" => 5 }
634
+ assert_equal 1, firm.rating
635
+ end
636
+
637
+ def test_customized_primary_key_remains_protected
638
+ subscriber = Subscriber.new(:nick => 'webster123', :name => 'nice try')
639
+ assert_nil subscriber.id
640
+
641
+ keyboard = Keyboard.new(:key_number => 9, :name => 'nice try')
642
+ assert_nil keyboard.id
643
+ end
644
+
645
+ def test_customized_primary_key_remains_protected_when_refered_to_as_id
646
+ subscriber = Subscriber.new(:id => 'webster123', :name => 'nice try')
647
+ assert_nil subscriber.id
648
+
649
+ keyboard = Keyboard.new(:id => 9, :name => 'nice try')
650
+ assert_nil keyboard.id
651
+ end
652
+
653
+ def test_mass_assignment_protection_on_defaults
654
+ firm = Firm.new
655
+ firm.attributes = { "id" => 5, "type" => "Client" }
656
+ assert_nil firm.id
657
+ assert_equal "Firm", firm[:type]
658
+ end
659
+
660
+ def test_mass_assignment_accessible
661
+ reply = Reply.new("title" => "hello", "content" => "world", "approved" => true)
662
+ reply.save
663
+
664
+ assert reply.approved?
665
+
666
+ reply.approved = false
667
+ reply.save
668
+
669
+ assert !reply.approved?
670
+ end
671
+
672
+ def test_mass_assignment_protection_inheritance
673
+ assert_nil LoosePerson.accessible_attributes
674
+ assert_equal [ :credit_rating, :administrator ], LoosePerson.protected_attributes
675
+
676
+ assert_nil LooseDescendant.accessible_attributes
677
+ assert_equal [ :credit_rating, :administrator, :phone_number ], LooseDescendant.protected_attributes
678
+
679
+ assert_nil TightPerson.protected_attributes
680
+ assert_equal [ :name, :address ], TightPerson.accessible_attributes
681
+
682
+ assert_nil TightDescendant.protected_attributes
683
+ assert_equal [ :name, :address, :phone_number ], TightDescendant.accessible_attributes
684
+ end
685
+
686
+ def test_multiparameter_attributes_on_date
687
+ attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "24" }
688
+ topic = Topic.find(1)
689
+ topic.attributes = attributes
690
+ # note that extra #to_date call allows test to pass for Oracle, which
691
+ # treats dates/times the same
692
+ assert_date_from_db Date.new(2004, 6, 24), topic.last_read.to_date
693
+ end
694
+
695
+ def test_multiparameter_attributes_on_date_with_empty_date
696
+ attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "" }
697
+ topic = Topic.find(1)
698
+ topic.attributes = attributes
699
+ # note that extra #to_date call allows test to pass for Oracle, which
700
+ # treats dates/times the same
701
+ assert_date_from_db Date.new(2004, 6, 1), topic.last_read.to_date
702
+ end
703
+
704
+ def test_multiparameter_attributes_on_date_with_all_empty
705
+ attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "" }
706
+ topic = Topic.find(1)
707
+ topic.attributes = attributes
708
+ assert_nil topic.last_read
709
+ end
710
+
711
+ def test_multiparameter_attributes_on_time
712
+ attributes = {
713
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
714
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
715
+ }
716
+ topic = Topic.find(1)
717
+ topic.attributes = attributes
718
+ assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
719
+ end
720
+
721
+ def test_multiparameter_attributes_on_time_with_empty_seconds
722
+ attributes = {
723
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
724
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => ""
725
+ }
726
+ topic = Topic.find(1)
727
+ topic.attributes = attributes
728
+ assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
729
+ end
730
+
731
+ def test_multiparameter_mass_assignment_protector
732
+ task = Task.new
733
+ time = Time.mktime(2000, 1, 1, 1)
734
+ task.starting = time
735
+ attributes = { "starting(1i)" => "2004", "starting(2i)" => "6", "starting(3i)" => "24" }
736
+ task.attributes = attributes
737
+ assert_equal time, task.starting
738
+ end
739
+
740
+ def test_multiparameter_assignment_of_aggregation
741
+ customer = Customer.new
742
+ address = Address.new("The Street", "The City", "The Country")
743
+ attributes = { "address(1)" => address.street, "address(2)" => address.city, "address(3)" => address.country }
744
+ customer.attributes = attributes
745
+ assert_equal address, customer.address
746
+ end
747
+
748
+ def test_attributes_on_dummy_time
749
+ # Oracle and SQL Server do not have a TIME datatype.
750
+ return true if current_adapter?(:SQLServerAdapter) ||
751
+ current_adapter?(:OracleAdapter)
752
+ if current_adapter?(:ODBCAdapter)
753
+ # Check for databases which don't have a true TIME datatype
754
+ return true if [:ingres, :oracle].include?(ActiveRecord::Base.connection.dbmsName)
755
+ end
756
+
757
+ attributes = {
758
+ "bonus_time" => "5:42:00AM"
759
+ }
760
+ topic = Topic.find(1)
761
+ topic.attributes = attributes
762
+ assert_equal Time.local(2000, 1, 1, 5, 42, 0), topic.bonus_time
763
+ end
764
+
765
+ def test_boolean
766
+ b_false = Booleantest.create({ "value" => false })
767
+ false_id = b_false.id
768
+ b_true = Booleantest.create({ "value" => true })
769
+ true_id = b_true.id
770
+
771
+ b_false = Booleantest.find(false_id)
772
+ assert !b_false.value?
773
+ b_true = Booleantest.find(true_id)
774
+ assert b_true.value?
775
+ end
776
+
777
+ def test_boolean_cast_from_string
778
+ b_false = Booleantest.create({ "value" => "0" })
779
+ false_id = b_false.id
780
+ b_true = Booleantest.create({ "value" => "1" })
781
+ true_id = b_true.id
782
+
783
+ b_false = Booleantest.find(false_id)
784
+ assert !b_false.value?
785
+ b_true = Booleantest.find(true_id)
786
+ assert b_true.value?
787
+ end
788
+
789
+ def test_clone
790
+ topic = Topic.find(1)
791
+ cloned_topic = nil
792
+ assert_nothing_raised { cloned_topic = topic.clone }
793
+ assert_equal topic.title, cloned_topic.title
794
+ assert cloned_topic.new_record?
795
+
796
+ # test if the attributes have been cloned
797
+ topic.title = "a"
798
+ cloned_topic.title = "b"
799
+ assert_equal "a", topic.title
800
+ assert_equal "b", cloned_topic.title
801
+
802
+ # test if the attribute values have been cloned
803
+ topic.title = {"a" => "b"}
804
+ cloned_topic = topic.clone
805
+ cloned_topic.title["a"] = "c"
806
+ assert_equal "b", topic.title["a"]
807
+
808
+ cloned_topic.save
809
+ assert !cloned_topic.new_record?
810
+ assert cloned_topic.id != topic.id
811
+ end
812
+
813
+ def test_clone_with_aggregate_of_same_name_as_attribute
814
+ dev = DeveloperWithAggregate.find(1)
815
+ assert_kind_of DeveloperSalary, dev.salary
816
+
817
+ clone = nil
818
+ assert_nothing_raised { clone = dev.clone }
819
+ assert_kind_of DeveloperSalary, clone.salary
820
+ assert_equal dev.salary.amount, clone.salary.amount
821
+ assert clone.new_record?
822
+
823
+ # test if the attributes have been cloned
824
+ original_amount = clone.salary.amount
825
+ dev.salary.amount = 1
826
+ assert_equal original_amount, clone.salary.amount
827
+
828
+ assert clone.save
829
+ assert !clone.new_record?
830
+ assert clone.id != dev.id
831
+ end
832
+
833
+ def test_clone_preserves_subtype
834
+ clone = nil
835
+ assert_nothing_raised { clone = Company.find(3).clone }
836
+ assert_kind_of Client, clone
837
+ end
838
+
839
+ def test_bignum
840
+ company = Company.find(1)
841
+ company.rating = 2147483647
842
+ company.save
843
+ company = Company.find(1)
844
+ assert_equal 2147483647, company.rating
845
+ end
846
+
847
+ # TODO: extend defaults tests to other databases!
848
+ if current_adapter?(:PostgreSQLAdapter)
849
+ def test_default
850
+ default = Default.new
851
+
852
+ # fixed dates / times
853
+ assert_equal Date.new(2004, 1, 1), default.fixed_date
854
+ assert_equal Time.local(2004, 1,1,0,0,0,0), default.fixed_time
855
+
856
+ # char types
857
+ assert_equal 'Y', default.char1
858
+ assert_equal 'a varchar field', default.char2
859
+ assert_equal 'a text field', default.char3
860
+ end
861
+
862
+ class Geometric < ActiveRecord::Base; end
863
+ def test_geometric_content
864
+
865
+ # accepted format notes:
866
+ # ()'s aren't required
867
+ # values can be a mix of float or integer
868
+
869
+ g = Geometric.new(
870
+ :a_point => '(5.0, 6.1)',
871
+ #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
872
+ :a_line_segment => '(2.0, 3), (5.5, 7.0)',
873
+ :a_box => '2.0, 3, 5.5, 7.0',
874
+ :a_path => '[(2.0, 3), (5.5, 7.0), (8.5, 11.0)]', # [ ] is an open path
875
+ :a_polygon => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))',
876
+ :a_circle => '<(5.3, 10.4), 2>'
877
+ )
878
+
879
+ assert g.save
880
+
881
+ # Reload and check that we have all the geometric attributes.
882
+ h = Geometric.find(g.id)
883
+
884
+ assert_equal '(5,6.1)', h.a_point
885
+ assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
886
+ assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
887
+ assert_equal '[(2,3),(5.5,7),(8.5,11)]', h.a_path
888
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
889
+ assert_equal '<(5.3,10.4),2>', h.a_circle
890
+
891
+ # use a geometric function to test for an open path
892
+ objs = Geometric.find_by_sql ["select isopen(a_path) from geometrics where id = ?", g.id]
893
+ assert_equal objs[0].isopen, 't'
894
+
895
+ # test alternate formats when defining the geometric types
896
+
897
+ g = Geometric.new(
898
+ :a_point => '5.0, 6.1',
899
+ #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
900
+ :a_line_segment => '((2.0, 3), (5.5, 7.0))',
901
+ :a_box => '(2.0, 3), (5.5, 7.0)',
902
+ :a_path => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', # ( ) is a closed path
903
+ :a_polygon => '2.0, 3, 5.5, 7.0, 8.5, 11.0',
904
+ :a_circle => '((5.3, 10.4), 2)'
905
+ )
906
+
907
+ assert g.save
908
+
909
+ # Reload and check that we have all the geometric attributes.
910
+ h = Geometric.find(g.id)
911
+
912
+ assert_equal '(5,6.1)', h.a_point
913
+ assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
914
+ assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
915
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path
916
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
917
+ assert_equal '<(5.3,10.4),2>', h.a_circle
918
+
919
+ # use a geometric function to test for an closed path
920
+ objs = Geometric.find_by_sql ["select isclosed(a_path) from geometrics where id = ?", g.id]
921
+ assert_equal objs[0].isclosed, 't'
922
+ end
923
+ end
924
+
925
+ def test_auto_id
926
+ auto = AutoId.new
927
+ auto.save
928
+ assert (auto.id > 0)
929
+ end
930
+
931
+ def quote_column_name(name)
932
+ "<#{name}>"
933
+ end
934
+
935
+ def test_quote_keys
936
+ ar = AutoId.new
937
+ source = {"foo" => "bar", "baz" => "quux"}
938
+ actual = ar.send(:quote_columns, self, source)
939
+ inverted = actual.invert
940
+ assert_equal("<foo>", inverted["bar"])
941
+ assert_equal("<baz>", inverted["quux"])
942
+ end
943
+
944
+ def test_sql_injection_via_find
945
+ assert_raises(ActiveRecord::RecordNotFound) do
946
+ Topic.find("123456 OR id > 0")
947
+ end
948
+
949
+ assert_raises(ActiveRecord::RecordNotFound) do
950
+ Topic.find(";;; this should raise an RecordNotFound error")
951
+ end
952
+ end
953
+
954
+ def test_column_name_properly_quoted
955
+ col_record = ColumnName.new
956
+ col_record.references = 40
957
+ assert col_record.save
958
+ col_record.references = 41
959
+ assert col_record.save
960
+ assert_not_nil c2 = ColumnName.find(col_record.id)
961
+ assert_equal(41, c2.references)
962
+ end
963
+
964
+ MyObject = Struct.new :attribute1, :attribute2
965
+
966
+ def test_serialized_attribute
967
+ myobj = MyObject.new('value1', 'value2')
968
+ topic = Topic.create("content" => myobj)
969
+ Topic.serialize("content", MyObject)
970
+ assert_equal(myobj, topic.content)
971
+ end
972
+
973
+ def test_serialized_attribute_with_class_constraint
974
+ myobj = MyObject.new('value1', 'value2')
975
+ topic = Topic.create("content" => myobj)
976
+ Topic.serialize(:content, Hash)
977
+
978
+ assert_raise(ActiveRecord::SerializationTypeMismatch) { Topic.find(topic.id).content }
979
+
980
+ settings = { "color" => "blue" }
981
+ Topic.find(topic.id).update_attribute("content", settings)
982
+ assert_equal(settings, Topic.find(topic.id).content)
983
+ Topic.serialize(:content)
984
+ end
985
+
986
+ def test_quote
987
+ if current_adapter?(:ODBCAdapter) && [:informix, :sybase].include?(ActiveRecord::Base.connection.dbmsName)
988
+ #Informix and Sybase only allow printable characters in VARCHAR columns.
989
+ author_name = "\\ \041 ' \n \\n \""
990
+ else
991
+ author_name = "\\ \001 ' \n \\n \""
992
+ end
993
+ topic = Topic.create('author_name' => author_name)
994
+ assert_equal author_name, Topic.find(topic.id).author_name
995
+ end
996
+
997
+ def test_class_level_destroy
998
+ should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
999
+ Topic.find(1).replies << should_be_destroyed_reply
1000
+
1001
+ Topic.destroy(1)
1002
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
1003
+ assert_raise(ActiveRecord::RecordNotFound) { Reply.find(should_be_destroyed_reply.id) }
1004
+ end
1005
+
1006
+ def test_class_level_delete
1007
+ should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
1008
+ Topic.find(1).replies << should_be_destroyed_reply
1009
+
1010
+ Topic.delete(1)
1011
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
1012
+ assert_nothing_raised { Reply.find(should_be_destroyed_reply.id) }
1013
+ end
1014
+
1015
+ def test_increment_attribute
1016
+ assert_equal 0, topics(:first).replies_count
1017
+ topics(:first).increment! :replies_count
1018
+ assert_equal 1, topics(:first, :reload).replies_count
1019
+
1020
+ topics(:first).increment(:replies_count).increment!(:replies_count)
1021
+ assert_equal 3, topics(:first, :reload).replies_count
1022
+ end
1023
+
1024
+ def test_increment_nil_attribute
1025
+ assert_nil topics(:first).parent_id
1026
+ topics(:first).increment! :parent_id
1027
+ assert_equal 1, topics(:first).parent_id
1028
+ end
1029
+
1030
+ def test_decrement_attribute
1031
+ topics(:first).increment(:replies_count).increment!(:replies_count)
1032
+ assert_equal 2, topics(:first).replies_count
1033
+
1034
+ topics(:first).decrement!(:replies_count)
1035
+ assert_equal 1, topics(:first, :reload).replies_count
1036
+
1037
+ topics(:first).decrement(:replies_count).decrement!(:replies_count)
1038
+ assert_equal -1, topics(:first, :reload).replies_count
1039
+ end
1040
+
1041
+ def test_toggle_attribute
1042
+ assert !topics(:first).approved?
1043
+ topics(:first).toggle!(:approved)
1044
+ assert topics(:first).approved?
1045
+ topic = topics(:first)
1046
+ topic.toggle(:approved)
1047
+ assert !topic.approved?
1048
+ topic.reload
1049
+ assert topic.approved?
1050
+ end
1051
+
1052
+ def test_reload
1053
+ t1 = Topic.find(1)
1054
+ t2 = Topic.find(1)
1055
+ t1.title = "something else"
1056
+ t1.save
1057
+ t2.reload
1058
+ assert_equal t1.title, t2.title
1059
+ end
1060
+
1061
+ def test_define_attr_method_with_value
1062
+ k = Class.new( ActiveRecord::Base )
1063
+ k.send(:define_attr_method, :table_name, "foo")
1064
+ assert_equal "foo", k.table_name
1065
+ end
1066
+
1067
+ def test_define_attr_method_with_block
1068
+ k = Class.new( ActiveRecord::Base )
1069
+ k.send(:define_attr_method, :primary_key) { "sys_" + original_primary_key }
1070
+ assert_equal "sys_id", k.primary_key
1071
+ end
1072
+
1073
+ def test_set_table_name_with_value
1074
+ k = Class.new( ActiveRecord::Base )
1075
+ k.table_name = "foo"
1076
+ assert_equal "foo", k.table_name
1077
+ k.set_table_name "bar"
1078
+ assert_equal "bar", k.table_name
1079
+ end
1080
+
1081
+ def test_set_table_name_with_block
1082
+ k = Class.new( ActiveRecord::Base )
1083
+ k.set_table_name { "ks" }
1084
+ assert_equal "ks", k.table_name
1085
+ end
1086
+
1087
+ def test_set_primary_key_with_value
1088
+ k = Class.new( ActiveRecord::Base )
1089
+ k.primary_key = "foo"
1090
+ assert_equal "foo", k.primary_key
1091
+ k.set_primary_key "bar"
1092
+ assert_equal "bar", k.primary_key
1093
+ end
1094
+
1095
+ def test_set_primary_key_with_block
1096
+ k = Class.new( ActiveRecord::Base )
1097
+ k.set_primary_key { "sys_" + original_primary_key }
1098
+ assert_equal "sys_id", k.primary_key
1099
+ end
1100
+
1101
+ def test_set_inheritance_column_with_value
1102
+ k = Class.new( ActiveRecord::Base )
1103
+ k.inheritance_column = "foo"
1104
+ assert_equal "foo", k.inheritance_column
1105
+ k.set_inheritance_column "bar"
1106
+ assert_equal "bar", k.inheritance_column
1107
+ end
1108
+
1109
+ def test_set_inheritance_column_with_block
1110
+ k = Class.new( ActiveRecord::Base )
1111
+ k.set_inheritance_column { original_inheritance_column + "_id" }
1112
+ assert_equal "type_id", k.inheritance_column
1113
+ end
1114
+
1115
+ def test_count_with_join
1116
+ res = Post.count_by_sql "SELECT COUNT(*) FROM posts LEFT JOIN comments ON posts.id=comments.post_id WHERE posts.#{QUOTED_TYPE} = 'Post'"
1117
+ res2 = nil
1118
+ assert_nothing_raised do
1119
+ res2 = Post.count("posts.#{QUOTED_TYPE} = 'Post'",
1120
+ "LEFT JOIN comments ON posts.id=comments.post_id")
1121
+ end
1122
+ assert_equal res, res2
1123
+
1124
+ res3 = nil
1125
+ assert_nothing_raised do
1126
+ res3 = Post.count(:conditions => "posts.#{QUOTED_TYPE} = 'Post'",
1127
+ :joins => "LEFT JOIN comments ON posts.id=comments.post_id")
1128
+ end
1129
+ assert_equal res, res3
1130
+
1131
+ res4 = Post.count_by_sql "SELECT COUNT(p.id) FROM posts p, comments c WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id"
1132
+ res5 = nil
1133
+ assert_nothing_raised do
1134
+ res5 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id",
1135
+ :joins => "p, comments c",
1136
+ :select => "p.id")
1137
+ end
1138
+
1139
+ assert_equal res4, res5
1140
+
1141
+ res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments c WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id"
1142
+ res7 = nil
1143
+ assert_nothing_raised do
1144
+ res7 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id",
1145
+ :joins => "p, comments c",
1146
+ :select => "p.id",
1147
+ :distinct => true)
1148
+ end
1149
+ assert_equal res6, res7
1150
+ end
1151
+
1152
+ def test_clear_association_cache_stored
1153
+ firm = Firm.find(1)
1154
+ assert_kind_of Firm, firm
1155
+
1156
+ firm.clear_association_cache
1157
+ assert_equal Firm.find(1).clients.collect{ |x| x.name }.sort, firm.clients.collect{ |x| x.name }.sort
1158
+ end
1159
+
1160
+ def test_clear_association_cache_new_record
1161
+ firm = Firm.new
1162
+ client_stored = Client.find(3)
1163
+ client_new = Client.new
1164
+ client_new.name = "The Joneses"
1165
+ clients = [ client_stored, client_new ]
1166
+
1167
+ firm.clients << clients
1168
+
1169
+ firm.clear_association_cache
1170
+
1171
+ assert_equal firm.clients.collect{ |x| x.name }.sort, clients.collect{ |x| x.name }.sort
1172
+ end
1173
+
1174
+ def test_interpolate_sql
1175
+ assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo@bar') }
1176
+ assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo bar) baz') }
1177
+ assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo bar} baz') }
1178
+ end
1179
+
1180
+ def test_scoped_find_conditions
1181
+ scoped_developers = Developer.with_scope(:find => { :conditions => 'salary > 90000' }) do
1182
+ Developer.find(:all, :conditions => 'id < 5')
1183
+ end
1184
+ assert !scoped_developers.include?(developers(:david)) # David's salary is less than 90,000
1185
+ assert_equal 3, scoped_developers.size
1186
+ end
1187
+
1188
+ def test_scoped_find_limit_offset
1189
+ scoped_developers = Developer.with_scope(:find => { :limit => 3, :offset => 2 }) do
1190
+ Developer.find(:all, :order => 'id')
1191
+ end
1192
+ assert !scoped_developers.include?(developers(:david))
1193
+ assert !scoped_developers.include?(developers(:jamis))
1194
+ assert_equal 3, scoped_developers.size
1195
+
1196
+ # Test without scoped find conditions to ensure we get the whole thing
1197
+ developers = Developer.find(:all, :order => 'id')
1198
+ assert_equal Developer.count, developers.size
1199
+ end
1200
+
1201
+ def test_base_class
1202
+ assert LoosePerson.abstract_class?
1203
+ assert !LooseDescendant.abstract_class?
1204
+ assert_equal LoosePerson, LoosePerson.base_class
1205
+ assert_equal LooseDescendant, LooseDescendant.base_class
1206
+ assert_equal TightPerson, TightPerson.base_class
1207
+ assert_equal TightPerson, TightDescendant.base_class
1208
+ end
1209
+
1210
+ def test_assert_queries
1211
+ query = lambda { ActiveRecord::Base.connection.execute 'select count(*) from developers' }
1212
+ assert_queries(2) { 2.times { query.call } }
1213
+ assert_queries 1, &query
1214
+ assert_no_queries { assert true }
1215
+ end
1216
+
1217
+ def test_to_xml
1218
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true)
1219
+ bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema
1220
+ written_on_in_current_timezone = topics(:first).written_on.xmlschema
1221
+ last_read_in_current_timezone = topics(:first).last_read.xmlschema
1222
+ assert_equal "<topic>", xml.first(7)
1223
+ assert xml.include?(%(<title>The First Topic</title>))
1224
+ assert xml.include?(%(<author-name>David</author-name>))
1225
+ assert xml.include?(%(<id type="integer">1</id>))
1226
+ assert xml.include?(%(<replies-count type="integer">0</replies-count>))
1227
+ assert xml.include?(%(<written-on type="datetime">#{written_on_in_current_timezone}</written-on>))
1228
+ assert xml.include?(%(<content>Have a nice day</content>))
1229
+ assert xml.include?(%(<author-email-address>david@loudthinking.com</author-email-address>))
1230
+ assert xml.include?(%(<parent-id></parent-id>))
1231
+ # Following databases don't have a true date type, only a composite datetime type
1232
+ if current_adapter?(:SybaseAdapter) or current_adapter?(:SQLServerAdapter) or
1233
+ current_adapter?(:ODBCAdapter) &&
1234
+ [:ingres,:oracle,:microsoftsqlserver].include?(ActiveRecord::Base.connection.dbmsName)
1235
+ assert xml.include?(%(<last-read type="datetime">#{last_read_in_current_timezone}</last-read>))
1236
+ else
1237
+ assert xml.include?(%(<last-read type="date">2004-04-15</last-read>))
1238
+ end
1239
+ # Following databases don't have a true boolean type
1240
+ unless current_adapter?(:OracleAdapter) || current_adapter?(:DB2Adapter)
1241
+ if current_adapter?(:ODBCAdapter) &&
1242
+ [:ingres,:virtuoso,:oracle,:mysql,:db2,:progress].include?(ActiveRecord::Base.connection.dbmsName)
1243
+ assert xml.include?(%(<approved type="integer">0</approved>)), "Approved should be an integer"
1244
+ else
1245
+ assert xml.include?(%(<approved type="boolean">false</approved>)), "Approved should be a boolean"
1246
+ end
1247
+ end
1248
+ # Oracle and DB2 don't have a true time-only field
1249
+ unless current_adapter?(:OracleAdapter) || current_adapter?(:DB2Adapter)
1250
+ assert xml.include?(%(<bonus-time type="datetime">#{bonus_time_in_current_timezone}</bonus-time>))
1251
+ end
1252
+ end
1253
+
1254
+ def test_to_xml_skipping_attributes
1255
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => :title)
1256
+ assert_equal "<topic>", xml.first(7)
1257
+ assert !xml.include?(%(<title>The First Topic</title>))
1258
+ assert xml.include?(%(<author-name>David</author-name>))
1259
+
1260
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [ :title, :author_name ])
1261
+ assert !xml.include?(%(<title>The First Topic</title>))
1262
+ assert !xml.include?(%(<author-name>David</author-name>))
1263
+ end
1264
+
1265
+ def test_to_xml_including_has_many_association
1266
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies)
1267
+ assert_equal "<topic>", xml.first(7)
1268
+ assert xml.include?(%(<replies><reply>))
1269
+ assert xml.include?(%(<title>The Second Topic's of the day</title>))
1270
+ end
1271
+
1272
+ def test_to_xml_including_belongs_to_association
1273
+ xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
1274
+ assert !xml.include?("<firm>")
1275
+
1276
+ xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
1277
+ assert xml.include?("<firm>")
1278
+ end
1279
+
1280
+ def test_to_xml_including_multiple_associations
1281
+ xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ])
1282
+ assert_equal "<firm>", xml.first(6)
1283
+ assert xml.include?(%(<account>))
1284
+ assert xml.include?(%(<clients><client>))
1285
+ end
1286
+
1287
+ def test_to_xml_including_multiple_associations_with_options
1288
+ xml = companies(:first_firm).to_xml(
1289
+ :indent => 0, :skip_instruct => true,
1290
+ :include => { :clients => { :only => :name } }
1291
+ )
1292
+
1293
+ assert_equal "<firm>", xml.first(6)
1294
+ assert xml.include?(%(<client><name>Summit</name></client>))
1295
+ assert xml.include?(%(<clients><client>))
1296
+ end
1297
+
1298
+ def test_except_attributes
1299
+ assert_equal(
1300
+ %w( author_name type id approved replies_count bonus_time written_on content author_email_address parent_id last_read),
1301
+ topics(:first).attributes(:except => :title).keys
1302
+ )
1303
+
1304
+ assert_equal(
1305
+ %w( replies_count bonus_time written_on content author_email_address parent_id last_read),
1306
+ topics(:first).attributes(:except => [ :title, :id, :type, :approved, :author_name ]).keys
1307
+ )
1308
+ end
1309
+
1310
+ def test_include_attributes
1311
+ assert_equal(%w( title ), topics(:first).attributes(:only => :title).keys)
1312
+ assert_equal(%w( title author_name type id approved ), topics(:first).attributes(:only => [ :title, :id, :type, :approved, :author_name ]).keys)
1313
+ end
1314
+
1315
+ def test_type_name_with_module_should_handle_beginning
1316
+ assert_equal 'ActiveRecord::Person', ActiveRecord::Base.send(:type_name_with_module, 'Person')
1317
+ assert_equal '::Person', ActiveRecord::Base.send(:type_name_with_module, '::Person')
1318
+ end
1319
+
1320
+ # FIXME: this test ought to run, but it needs to run sandboxed so that it
1321
+ # doesn't b0rk the current test environment by undefing everything.
1322
+ #
1323
+ #def test_dev_mode_memory_leak
1324
+ # counts = []
1325
+ # 2.times do
1326
+ # require_dependency 'fixtures/company'
1327
+ # Firm.find(:first)
1328
+ # Dependencies.clear
1329
+ # ActiveRecord::Base.reset_subclasses
1330
+ # Dependencies.remove_subclasses_for(ActiveRecord::Base)
1331
+ #
1332
+ # GC.start
1333
+ #
1334
+ # count = 0
1335
+ # ObjectSpace.each_object(Proc) { count += 1 }
1336
+ # counts << count
1337
+ # end
1338
+ # assert counts.last <= counts.first,
1339
+ # "expected last count (#{counts.last}) to be <= first count (#{counts.first})"
1340
+ #end
1341
+
1342
+ private
1343
+ def assert_readers(model, exceptions)
1344
+ expected_readers = Set.new(model.column_names - (model.serialized_attributes.keys + ['id']))
1345
+ expected_readers += expected_readers.map { |col| "#{col}?" }
1346
+ expected_readers -= exceptions
1347
+ assert_equal expected_readers, model.read_methods
1348
+ end
1349
+ end