activerecord 1.0.0 → 3.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 (178) hide show
  1. data/CHANGELOG +5518 -76
  2. data/README.rdoc +222 -0
  3. data/examples/performance.rb +162 -0
  4. data/examples/simple.rb +14 -0
  5. data/lib/active_record/aggregations.rb +192 -80
  6. data/lib/active_record/association_preload.rb +403 -0
  7. data/lib/active_record/associations/association_collection.rb +545 -53
  8. data/lib/active_record/associations/association_proxy.rb +295 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +78 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +127 -36
  12. data/lib/active_record/associations/has_many_association.rb +108 -84
  13. data/lib/active_record/associations/has_many_through_association.rb +116 -0
  14. data/lib/active_record/associations/has_one_association.rb +143 -0
  15. data/lib/active_record/associations/has_one_through_association.rb +40 -0
  16. data/lib/active_record/associations/through_association_scope.rb +154 -0
  17. data/lib/active_record/associations.rb +2086 -368
  18. data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
  19. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  20. data/lib/active_record/attribute_methods/primary_key.rb +50 -0
  21. data/lib/active_record/attribute_methods/query.rb +39 -0
  22. data/lib/active_record/attribute_methods/read.rb +116 -0
  23. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -0
  24. data/lib/active_record/attribute_methods/write.rb +37 -0
  25. data/lib/active_record/attribute_methods.rb +60 -0
  26. data/lib/active_record/autosave_association.rb +369 -0
  27. data/lib/active_record/base.rb +1603 -721
  28. data/lib/active_record/callbacks.rb +176 -225
  29. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +365 -0
  30. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
  31. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  32. data/lib/active_record/connection_adapters/abstract/database_statements.rb +329 -0
  33. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  34. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -0
  35. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
  36. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +543 -0
  37. data/lib/active_record/connection_adapters/abstract_adapter.rb +165 -279
  38. data/lib/active_record/connection_adapters/mysql_adapter.rb +594 -82
  39. data/lib/active_record/connection_adapters/postgresql_adapter.rb +988 -135
  40. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -0
  41. data/lib/active_record/connection_adapters/sqlite_adapter.rb +365 -71
  42. data/lib/active_record/counter_cache.rb +115 -0
  43. data/lib/active_record/dynamic_finder_match.rb +53 -0
  44. data/lib/active_record/dynamic_scope_match.rb +32 -0
  45. data/lib/active_record/errors.rb +172 -0
  46. data/lib/active_record/fixtures.rb +941 -105
  47. data/lib/active_record/locale/en.yml +40 -0
  48. data/lib/active_record/locking/optimistic.rb +172 -0
  49. data/lib/active_record/locking/pessimistic.rb +55 -0
  50. data/lib/active_record/log_subscriber.rb +48 -0
  51. data/lib/active_record/migration.rb +617 -0
  52. data/lib/active_record/named_scope.rb +138 -0
  53. data/lib/active_record/nested_attributes.rb +417 -0
  54. data/lib/active_record/observer.rb +105 -36
  55. data/lib/active_record/persistence.rb +291 -0
  56. data/lib/active_record/query_cache.rb +36 -0
  57. data/lib/active_record/railtie.rb +91 -0
  58. data/lib/active_record/railties/controller_runtime.rb +38 -0
  59. data/lib/active_record/railties/databases.rake +512 -0
  60. data/lib/active_record/reflection.rb +364 -87
  61. data/lib/active_record/relation/batches.rb +89 -0
  62. data/lib/active_record/relation/calculations.rb +286 -0
  63. data/lib/active_record/relation/finder_methods.rb +355 -0
  64. data/lib/active_record/relation/predicate_builder.rb +41 -0
  65. data/lib/active_record/relation/query_methods.rb +261 -0
  66. data/lib/active_record/relation/spawn_methods.rb +112 -0
  67. data/lib/active_record/relation.rb +393 -0
  68. data/lib/active_record/schema.rb +59 -0
  69. data/lib/active_record/schema_dumper.rb +195 -0
  70. data/lib/active_record/serialization.rb +60 -0
  71. data/lib/active_record/serializers/xml_serializer.rb +244 -0
  72. data/lib/active_record/session_store.rb +340 -0
  73. data/lib/active_record/test_case.rb +67 -0
  74. data/lib/active_record/timestamp.rb +88 -0
  75. data/lib/active_record/transactions.rb +329 -75
  76. data/lib/active_record/validations/associated.rb +48 -0
  77. data/lib/active_record/validations/uniqueness.rb +185 -0
  78. data/lib/active_record/validations.rb +58 -179
  79. data/lib/active_record/version.rb +9 -0
  80. data/lib/active_record.rb +100 -24
  81. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  82. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  83. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  84. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  85. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  86. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  87. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  88. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  89. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  90. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  91. data/lib/rails/generators/active_record.rb +27 -0
  92. metadata +216 -158
  93. data/README +0 -361
  94. data/RUNNING_UNIT_TESTS +0 -36
  95. data/dev-utils/eval_debugger.rb +0 -9
  96. data/examples/associations.rb +0 -87
  97. data/examples/shared_setup.rb +0 -15
  98. data/examples/validation.rb +0 -88
  99. data/install.rb +0 -60
  100. data/lib/active_record/deprecated_associations.rb +0 -70
  101. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  102. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  103. data/lib/active_record/support/clean_logger.rb +0 -10
  104. data/lib/active_record/support/inflector.rb +0 -70
  105. data/lib/active_record/vendor/mysql.rb +0 -1117
  106. data/lib/active_record/vendor/simple.rb +0 -702
  107. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  108. data/lib/active_record/wrappings.rb +0 -59
  109. data/rakefile +0 -122
  110. data/test/abstract_unit.rb +0 -16
  111. data/test/aggregations_test.rb +0 -34
  112. data/test/all.sh +0 -8
  113. data/test/associations_test.rb +0 -477
  114. data/test/base_test.rb +0 -513
  115. data/test/class_inheritable_attributes_test.rb +0 -33
  116. data/test/connections/native_mysql/connection.rb +0 -24
  117. data/test/connections/native_postgresql/connection.rb +0 -24
  118. data/test/connections/native_sqlite/connection.rb +0 -24
  119. data/test/deprecated_associations_test.rb +0 -336
  120. data/test/finder_test.rb +0 -67
  121. data/test/fixtures/accounts/signals37 +0 -3
  122. data/test/fixtures/accounts/unknown +0 -2
  123. data/test/fixtures/auto_id.rb +0 -4
  124. data/test/fixtures/column_name.rb +0 -3
  125. data/test/fixtures/companies/first_client +0 -6
  126. data/test/fixtures/companies/first_firm +0 -4
  127. data/test/fixtures/companies/second_client +0 -6
  128. data/test/fixtures/company.rb +0 -37
  129. data/test/fixtures/company_in_module.rb +0 -33
  130. data/test/fixtures/course.rb +0 -3
  131. data/test/fixtures/courses/java +0 -2
  132. data/test/fixtures/courses/ruby +0 -2
  133. data/test/fixtures/customer.rb +0 -30
  134. data/test/fixtures/customers/david +0 -6
  135. data/test/fixtures/db_definitions/mysql.sql +0 -96
  136. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  137. data/test/fixtures/db_definitions/postgresql.sql +0 -113
  138. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  139. data/test/fixtures/db_definitions/sqlite.sql +0 -85
  140. data/test/fixtures/db_definitions/sqlite2.sql +0 -4
  141. data/test/fixtures/default.rb +0 -2
  142. data/test/fixtures/developer.rb +0 -8
  143. data/test/fixtures/developers/david +0 -2
  144. data/test/fixtures/developers/jamis +0 -2
  145. data/test/fixtures/developers_projects/david_action_controller +0 -2
  146. data/test/fixtures/developers_projects/david_active_record +0 -2
  147. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  148. data/test/fixtures/entrant.rb +0 -3
  149. data/test/fixtures/entrants/first +0 -3
  150. data/test/fixtures/entrants/second +0 -3
  151. data/test/fixtures/entrants/third +0 -3
  152. data/test/fixtures/fixture_database.sqlite +0 -0
  153. data/test/fixtures/fixture_database_2.sqlite +0 -0
  154. data/test/fixtures/movie.rb +0 -5
  155. data/test/fixtures/movies/first +0 -2
  156. data/test/fixtures/movies/second +0 -2
  157. data/test/fixtures/project.rb +0 -3
  158. data/test/fixtures/projects/action_controller +0 -2
  159. data/test/fixtures/projects/active_record +0 -2
  160. data/test/fixtures/reply.rb +0 -21
  161. data/test/fixtures/subscriber.rb +0 -5
  162. data/test/fixtures/subscribers/first +0 -2
  163. data/test/fixtures/subscribers/second +0 -2
  164. data/test/fixtures/topic.rb +0 -20
  165. data/test/fixtures/topics/first +0 -9
  166. data/test/fixtures/topics/second +0 -8
  167. data/test/fixtures_test.rb +0 -20
  168. data/test/inflector_test.rb +0 -104
  169. data/test/inheritance_test.rb +0 -125
  170. data/test/lifecycle_test.rb +0 -110
  171. data/test/modules_test.rb +0 -21
  172. data/test/multiple_db_test.rb +0 -46
  173. data/test/pk_test.rb +0 -57
  174. data/test/reflection_test.rb +0 -78
  175. data/test/thread_safety_test.rb +0 -33
  176. data/test/transactions_test.rb +0 -83
  177. data/test/unconnected_test.rb +0 -24
  178. data/test/validations_test.rb +0 -126
@@ -0,0 +1,53 @@
1
+ require 'active_record/connection_adapters/sqlite_adapter'
2
+
3
+ module ActiveRecord
4
+ class Base
5
+ # sqlite3 adapter reuses sqlite_connection.
6
+ def self.sqlite3_connection(config) # :nodoc:
7
+ # Require database.
8
+ unless config[:database]
9
+ raise ArgumentError, "No database file specified. Missing argument: database"
10
+ end
11
+
12
+ # Allow database path relative to Rails.root, but only if
13
+ # the database path is not the special path that tells
14
+ # Sqlite to build a database only in memory.
15
+ if defined?(Rails.root) && ':memory:' != config[:database]
16
+ config[:database] = File.expand_path(config[:database], Rails.root)
17
+ end
18
+
19
+ unless 'sqlite3' == config[:adapter]
20
+ raise ArgumentError, 'adapter name should be "sqlite3"'
21
+ end
22
+
23
+ unless self.class.const_defined?(:SQLite3)
24
+ require_library_or_gem(config[:adapter])
25
+ end
26
+
27
+ db = SQLite3::Database.new(
28
+ config[:database],
29
+ :results_as_hash => true
30
+ )
31
+
32
+ db.busy_timeout(config[:timeout]) unless config[:timeout].nil?
33
+
34
+ ConnectionAdapters::SQLite3Adapter.new(db, logger, config)
35
+ end
36
+ end
37
+
38
+ module ConnectionAdapters #:nodoc:
39
+ class SQLite3Adapter < SQLiteAdapter # :nodoc:
40
+
41
+ # Returns the current database encoding format as a string, eg: 'UTF-8'
42
+ def encoding
43
+ if @connection.respond_to?(:encoding)
44
+ @connection.encoding.to_s
45
+ else
46
+ encoding = @connection.execute('PRAGMA encoding')
47
+ encoding[0]['encoding']
48
+ end
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -1,107 +1,401 @@
1
- # sqlite_adapter.rb
2
- # author: Luke Holden <lholden@cablelan.net>
3
-
4
1
  require 'active_record/connection_adapters/abstract_adapter'
2
+ require 'active_support/core_ext/kernel/requires'
3
+
4
+ module ActiveRecord
5
+ module ConnectionAdapters #:nodoc:
6
+ class SQLiteColumn < Column #:nodoc:
7
+ class << self
8
+ def string_to_binary(value)
9
+ value.gsub(/\0|\%/n) do |b|
10
+ case b
11
+ when "\0" then "%00"
12
+ when "%" then "%25"
13
+ end
14
+ end
15
+ end
5
16
 
6
- begin
7
- require 'sqlite' unless self.class.const_defined?(:SQLite)
17
+ def binary_to_string(value)
18
+ if value.respond_to?(:force_encoding) && value.encoding != Encoding::ASCII_8BIT
19
+ value = value.force_encoding(Encoding::ASCII_8BIT)
20
+ end
8
21
 
9
- module ActiveRecord
10
- class Base
11
- # Establishes a connection to the database that's used by all Active Record objects
12
- def self.sqlite_connection(config) # :nodoc:
13
- symbolize_strings_in_hash(config)
14
- unless config.has_key?(:dbfile)
15
- raise ArgumentError, "No database file specified. Missing argument: dbfile"
22
+ value.gsub(/%00|%25/n) do |b|
23
+ case b
24
+ when "%00" then "\0"
25
+ when "%25" then "%"
26
+ end
27
+ end
16
28
  end
29
+ end
30
+ end
17
31
 
18
- db = SQLite::Database.new(config[:dbfile], 0)
32
+ # The SQLite adapter works with both the 2.x and 3.x series of SQLite with the sqlite-ruby
33
+ # drivers (available both as gems and from http://rubyforge.org/projects/sqlite-ruby/).
34
+ #
35
+ # Options:
36
+ #
37
+ # * <tt>:database</tt> - Path to the database file.
38
+ class SQLiteAdapter < AbstractAdapter
39
+ class Version
40
+ include Comparable
41
+
42
+ def initialize(version_string)
43
+ @version = version_string.split('.').map { |v| v.to_i }
44
+ end
19
45
 
20
- db.show_datatypes = "ON" if !defined? SQLite::Version
21
- db.results_as_hash = true if defined? SQLite::Version
22
- db.type_translation = false
46
+ def <=>(version_string)
47
+ @version <=> version_string.split('.').map { |v| v.to_i }
48
+ end
49
+ end
23
50
 
24
- ConnectionAdapters::SQLiteAdapter.new(db, logger)
51
+ def initialize(connection, logger, config)
52
+ super(connection, logger)
53
+ @config = config
54
+ end
55
+
56
+ def adapter_name #:nodoc:
57
+ 'SQLite'
58
+ end
59
+
60
+ def supports_ddl_transactions?
61
+ sqlite_version >= '2.0.0'
62
+ end
63
+
64
+ def supports_migrations? #:nodoc:
65
+ true
66
+ end
67
+
68
+ def supports_primary_key? #:nodoc:
69
+ true
70
+ end
71
+
72
+ def requires_reloading?
73
+ true
74
+ end
75
+
76
+ def supports_add_column?
77
+ sqlite_version >= '3.1.6'
78
+ end
79
+
80
+ def disconnect!
81
+ super
82
+ @connection.close rescue nil
83
+ end
84
+
85
+ def supports_count_distinct? #:nodoc:
86
+ sqlite_version >= '3.2.6'
25
87
  end
26
- end
27
88
 
28
- module ConnectionAdapters
29
- class SQLiteAdapter < AbstractAdapter # :nodoc:
30
- def select_all(sql, name = nil)
31
- select(sql, name)
89
+ def supports_autoincrement? #:nodoc:
90
+ sqlite_version >= '3.1.0'
91
+ end
92
+
93
+ def native_database_types #:nodoc:
94
+ {
95
+ :primary_key => default_primary_key_type,
96
+ :string => { :name => "varchar", :limit => 255 },
97
+ :text => { :name => "text" },
98
+ :integer => { :name => "integer" },
99
+ :float => { :name => "float" },
100
+ :decimal => { :name => "decimal" },
101
+ :datetime => { :name => "datetime" },
102
+ :timestamp => { :name => "datetime" },
103
+ :time => { :name => "time" },
104
+ :date => { :name => "date" },
105
+ :binary => { :name => "blob" },
106
+ :boolean => { :name => "boolean" }
107
+ }
108
+ end
109
+
110
+
111
+ # QUOTING ==================================================
112
+
113
+ def quote_string(s) #:nodoc:
114
+ @connection.class.quote(s)
115
+ end
116
+
117
+ def quote_column_name(name) #:nodoc:
118
+ %Q("#{name}")
119
+ end
120
+
121
+ # Quote date/time values for use in SQL input. Includes microseconds
122
+ # if the value is a Time responding to usec.
123
+ def quoted_date(value) #:nodoc:
124
+ if value.acts_like?(:time) && value.respond_to?(:usec)
125
+ "#{super}.#{sprintf("%06d", value.usec)}"
126
+ else
127
+ super
32
128
  end
129
+ end
130
+
131
+
132
+ # DATABASE STATEMENTS ======================================
133
+
134
+ def execute(sql, name = nil) #:nodoc:
135
+ log(sql, name) { @connection.execute(sql) }
136
+ end
33
137
 
34
- def select_one(sql, name = nil)
35
- result = select(sql, name)
36
- result.nil? ? nil : result.first
138
+ def update_sql(sql, name = nil) #:nodoc:
139
+ super
140
+ @connection.changes
141
+ end
142
+
143
+ def delete_sql(sql, name = nil) #:nodoc:
144
+ sql += " WHERE 1=1" unless sql =~ /WHERE/i
145
+ super sql, name
146
+ end
147
+
148
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
149
+ super || @connection.last_insert_row_id
150
+ end
151
+ alias :create :insert_sql
152
+
153
+ def select_rows(sql, name = nil)
154
+ execute(sql, name).map do |row|
155
+ (0...(row.size / 2)).map { |i| row[i] }
37
156
  end
157
+ end
158
+
159
+ def begin_db_transaction #:nodoc:
160
+ @connection.transaction
161
+ end
162
+
163
+ def commit_db_transaction #:nodoc:
164
+ @connection.commit
165
+ end
166
+
167
+ def rollback_db_transaction #:nodoc:
168
+ @connection.rollback
169
+ end
170
+
171
+ # SCHEMA STATEMENTS ========================================
172
+
173
+ def tables(name = nil) #:nodoc:
174
+ sql = <<-SQL
175
+ SELECT name
176
+ FROM sqlite_master
177
+ WHERE type = 'table' AND NOT name = 'sqlite_sequence'
178
+ SQL
38
179
 
39
- def columns(table_name, name = nil)
40
- table_structure(table_name).inject([]) do |columns, field|
41
- columns << Column.new(field['name'], field['dflt_value'], field['type'])
42
- columns
180
+ execute(sql, name).map do |row|
181
+ row['name']
182
+ end
183
+ end
184
+
185
+ def columns(table_name, name = nil) #:nodoc:
186
+ table_structure(table_name).map do |field|
187
+ SQLiteColumn.new(field['name'], field['dflt_value'], field['type'], field['notnull'].to_i == 0)
188
+ end
189
+ end
190
+
191
+ def indexes(table_name, name = nil) #:nodoc:
192
+ execute("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row|
193
+ IndexDefinition.new(
194
+ table_name,
195
+ row['name'],
196
+ row['unique'].to_i != 0,
197
+ execute("PRAGMA index_info('#{row['name']}')").map { |col|
198
+ col['name']
199
+ })
200
+ end
201
+ end
202
+
203
+ def primary_key(table_name) #:nodoc:
204
+ column = table_structure(table_name).find { |field|
205
+ field['pk'].to_i == 1
206
+ }
207
+ column && column['name']
208
+ end
209
+
210
+ def remove_index!(table_name, index_name) #:nodoc:
211
+ execute "DROP INDEX #{quote_column_name(index_name)}"
212
+ end
213
+
214
+ def rename_table(name, new_name)
215
+ execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
216
+ end
217
+
218
+ # See: http://www.sqlite.org/lang_altertable.html
219
+ # SQLite has an additional restriction on the ALTER TABLE statement
220
+ def valid_alter_table_options( type, options)
221
+ type.to_sym != :primary_key
222
+ end
223
+
224
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
225
+ if supports_add_column? && valid_alter_table_options( type, options )
226
+ super(table_name, column_name, type, options)
227
+ else
228
+ alter_table(table_name) do |definition|
229
+ definition.column(column_name, type, options)
230
+ end
231
+ end
232
+ end
233
+
234
+ def remove_column(table_name, *column_names) #:nodoc:
235
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
236
+ column_names.flatten.each do |column_name|
237
+ alter_table(table_name) do |definition|
238
+ definition.columns.delete(definition[column_name])
239
+ end
240
+ end
241
+ end
242
+ alias :remove_columns :remove_column
243
+
244
+ def change_column_default(table_name, column_name, default) #:nodoc:
245
+ alter_table(table_name) do |definition|
246
+ definition[column_name].default = default
247
+ end
248
+ end
249
+
250
+ def change_column_null(table_name, column_name, null, default = nil)
251
+ unless null || default.nil?
252
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
253
+ end
254
+ alter_table(table_name) do |definition|
255
+ definition[column_name].null = null
256
+ end
257
+ end
258
+
259
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
260
+ alter_table(table_name) do |definition|
261
+ include_default = options_include_default?(options)
262
+ definition[column_name].instance_eval do
263
+ self.type = type
264
+ self.limit = options[:limit] if options.include?(:limit)
265
+ self.default = options[:default] if include_default
266
+ self.null = options[:null] if options.include?(:null)
43
267
  end
44
268
  end
269
+ end
45
270
 
46
- def insert(sql, name = nil, pk = nil, id_value = nil)
47
- execute(sql, name = nil)
48
- id_value || @connection.send( defined?( SQLite::Version ) ? :last_insert_row_id : :last_insert_rowid )
271
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
272
+ unless columns(table_name).detect{|c| c.name == column_name.to_s }
273
+ raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}"
49
274
  end
275
+ alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
276
+ end
50
277
 
51
- def execute(sql, name = nil)
52
- log(sql, name, @connection) do |connection|
53
- if defined?( SQLite::Version )
54
- case sql
55
- when "BEGIN" then connection.transaction
56
- when "COMMIT" then connection.commit
57
- when "ROLLBACK" then connection.rollback
58
- else connection.execute(sql)
59
- end
60
- else
61
- connection.execute( sql )
278
+ def empty_insert_statement_value
279
+ "VALUES(NULL)"
280
+ end
281
+
282
+ protected
283
+ def select(sql, name = nil) #:nodoc:
284
+ execute(sql, name).map do |row|
285
+ record = {}
286
+ row.each do |key, value|
287
+ record[key.sub(/^"?\w+"?\./, '')] = value if key.is_a?(String)
62
288
  end
289
+ record
63
290
  end
64
291
  end
65
292
 
66
- alias_method :update, :execute
67
- alias_method :delete, :execute
293
+ def table_structure(table_name)
294
+ structure = @connection.table_info(quote_table_name(table_name))
295
+ raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
296
+ structure
297
+ end
298
+
299
+ def alter_table(table_name, options = {}) #:nodoc:
300
+ altered_table_name = "altered_#{table_name}"
301
+ caller = lambda {|definition| yield definition if block_given?}
68
302
 
69
- def begin_db_transaction() execute "BEGIN" end
70
- def commit_db_transaction() execute "COMMIT" end
71
- def rollback_db_transaction() execute "ROLLBACK" end
303
+ transaction do
304
+ move_table(table_name, altered_table_name,
305
+ options.merge(:temporary => true))
306
+ move_table(altered_table_name, table_name, &caller)
307
+ end
308
+ end
309
+
310
+ def move_table(from, to, options = {}, &block) #:nodoc:
311
+ copy_table(from, to, options, &block)
312
+ drop_table(from)
313
+ end
314
+
315
+ def copy_table(from, to, options = {}) #:nodoc:
316
+ options = options.merge(:id => (!columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s))
317
+ create_table(to, options) do |definition|
318
+ @definition = definition
319
+ columns(from).each do |column|
320
+ column_name = options[:rename] ?
321
+ (options[:rename][column.name] ||
322
+ options[:rename][column.name.to_sym] ||
323
+ column.name) : column.name
324
+
325
+ @definition.column(column_name, column.type,
326
+ :limit => column.limit, :default => column.default,
327
+ :null => column.null)
328
+ end
329
+ @definition.primary_key(primary_key(from)) if primary_key(from)
330
+ yield @definition if block_given?
331
+ end
72
332
 
73
- def quote_column_name(name)
74
- return "'#{name}'"
333
+ copy_table_indexes(from, to, options[:rename] || {})
334
+ copy_table_contents(from, to,
335
+ @definition.columns.map {|column| column.name},
336
+ options[:rename] || {})
75
337
  end
76
338
 
77
- private
78
- def select(sql, name = nil)
79
- results = nil
80
- log(sql, name, @connection) { |connection| results = connection.execute(sql) }
339
+ def copy_table_indexes(from, to, rename = {}) #:nodoc:
340
+ indexes(from).each do |index|
341
+ name = index.name
342
+ if to == "altered_#{from}"
343
+ name = "temp_#{name}"
344
+ elsif from == "altered_#{to}"
345
+ name = name[5..-1]
346
+ end
81
347
 
82
- rows = []
348
+ to_column_names = columns(to).map { |c| c.name }
349
+ columns = index.columns.map {|c| rename[c] || c }.select do |column|
350
+ to_column_names.include?(column)
351
+ end
83
352
 
84
- results.each do |row|
85
- hash_only_row = {}
86
- row.each_key do |key|
87
- hash_only_row[key.gsub(/\w\./, "")] = row[key] unless key.class == Fixnum
88
- end
89
- rows << hash_only_row
353
+ unless columns.empty?
354
+ # index name can't be the same
355
+ opts = { :name => name.gsub(/_(#{from})_/, "_#{to}_") }
356
+ opts[:unique] = true if index.unique
357
+ add_index(to, columns, opts)
90
358
  end
359
+ end
360
+ end
361
+
362
+ def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
363
+ column_mappings = Hash[*columns.map {|name| [name, name]}.flatten]
364
+ rename.inject(column_mappings) {|map, a| map[a.last] = a.first; map}
365
+ from_columns = columns(from).collect {|col| col.name}
366
+ columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
367
+ quoted_columns = columns.map { |col| quote_column_name(col) } * ','
91
368
 
92
- return rows
369
+ quoted_to = quote_table_name(to)
370
+ @connection.execute "SELECT * FROM #{quote_table_name(from)}" do |row|
371
+ sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
372
+ sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
373
+ sql << ')'
374
+ @connection.execute sql
93
375
  end
376
+ end
94
377
 
95
- def table_structure(table_name)
96
- sql = "PRAGMA table_info(#{table_name});"
97
- results = nil
98
- log(sql, nil, @connection) { |connection| results = connection.execute(sql) }
99
- return results
378
+ def sqlite_version
379
+ @sqlite_version ||= SQLiteAdapter::Version.new(select_value('select sqlite_version(*)'))
380
+ end
381
+
382
+ def default_primary_key_type
383
+ if supports_autoincrement?
384
+ 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'
385
+ else
386
+ 'INTEGER PRIMARY KEY NOT NULL'
100
387
  end
101
- end
388
+ end
389
+
390
+ def translate_exception(exception, message)
391
+ case exception.message
392
+ when /column(s)? .* (is|are) not unique/
393
+ RecordNotUnique.new(message, exception)
394
+ else
395
+ super
396
+ end
397
+ end
398
+
102
399
  end
103
400
  end
104
- rescue LoadError
105
- retry if require('rubygems') rescue LoadError
106
- # SQLite driver is not availible
107
401
  end
@@ -0,0 +1,115 @@
1
+ module ActiveRecord
2
+ # = Active Record Counter Cache
3
+ module CounterCache
4
+ # Resets one or more counter caches to their correct value using an SQL
5
+ # count query. This is useful when adding new counter caches, or if the
6
+ # counter has been corrupted or modified directly by SQL.
7
+ #
8
+ # ==== Parameters
9
+ #
10
+ # * +id+ - The id of the object you wish to reset a counter on.
11
+ # * +counters+ - One or more counter names to reset
12
+ #
13
+ # ==== Examples
14
+ #
15
+ # # For Post with id #1 records reset the comments_count
16
+ # Post.reset_counters(1, :comments)
17
+ def reset_counters(id, *counters)
18
+ object = find(id)
19
+ counters.each do |association|
20
+ has_many_association = reflect_on_association(association.to_sym)
21
+
22
+ expected_name = if has_many_association.options[:as]
23
+ has_many_association.options[:as].to_s.classify
24
+ else
25
+ self.name
26
+ end
27
+
28
+ child_class = has_many_association.klass
29
+ belongs_to = child_class.reflect_on_all_associations(:belongs_to)
30
+ reflection = belongs_to.find { |e| e.class_name == expected_name }
31
+ counter_name = reflection.counter_cache_column
32
+
33
+ self.unscoped.where(arel_table[self.primary_key].eq(object.id)).arel.update({
34
+ arel_table[counter_name] => object.send(association).count
35
+ })
36
+ end
37
+ return true
38
+ end
39
+
40
+ # A generic "counter updater" implementation, intended primarily to be
41
+ # used by increment_counter and decrement_counter, but which may also
42
+ # be useful on its own. It simply does a direct SQL update for the record
43
+ # with the given ID, altering the given hash of counters by the amount
44
+ # given by the corresponding value:
45
+ #
46
+ # ==== Parameters
47
+ #
48
+ # * +id+ - The id of the object you wish to update a counter on or an Array of ids.
49
+ # * +counters+ - An Array of Hashes containing the names of the fields
50
+ # to update as keys and the amount to update the field by as values.
51
+ #
52
+ # ==== Examples
53
+ #
54
+ # # For the Post with id of 5, decrement the comment_count by 1, and
55
+ # # increment the action_count by 1
56
+ # Post.update_counters 5, :comment_count => -1, :action_count => 1
57
+ # # Executes the following SQL:
58
+ # # UPDATE posts
59
+ # # SET comment_count = comment_count - 1,
60
+ # # action_count = action_count + 1
61
+ # # WHERE id = 5
62
+ #
63
+ # # For the Posts with id of 10 and 15, increment the comment_count by 1
64
+ # Post.update_counters [10, 15], :comment_count => 1
65
+ # # Executes the following SQL:
66
+ # # UPDATE posts
67
+ # # SET comment_count = comment_count + 1,
68
+ # # WHERE id IN (10, 15)
69
+ def update_counters(id, counters)
70
+ updates = counters.map do |counter_name, value|
71
+ operator = value < 0 ? '-' : '+'
72
+ quoted_column = connection.quote_column_name(counter_name)
73
+ "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
74
+ end
75
+
76
+ update_all(updates.join(', '), primary_key => id )
77
+ end
78
+
79
+ # Increment a number field by one, usually representing a count.
80
+ #
81
+ # This is used for caching aggregate values, so that they don't need to be computed every time.
82
+ # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
83
+ # shown it would have to run an SQL query to find how many posts and comments there are.
84
+ #
85
+ # ==== Parameters
86
+ #
87
+ # * +counter_name+ - The name of the field that should be incremented.
88
+ # * +id+ - The id of the object that should be incremented.
89
+ #
90
+ # ==== Examples
91
+ #
92
+ # # Increment the post_count column for the record with an id of 5
93
+ # DiscussionBoard.increment_counter(:post_count, 5)
94
+ def increment_counter(counter_name, id)
95
+ update_counters(id, counter_name => 1)
96
+ end
97
+
98
+ # Decrement a number field by one, usually representing a count.
99
+ #
100
+ # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
101
+ #
102
+ # ==== Parameters
103
+ #
104
+ # * +counter_name+ - The name of the field that should be decremented.
105
+ # * +id+ - The id of the object that should be decremented.
106
+ #
107
+ # ==== Examples
108
+ #
109
+ # # Decrement the post_count column for the record with an id of 5
110
+ # DiscussionBoard.decrement_counter(:post_count, 5)
111
+ def decrement_counter(counter_name, id)
112
+ update_counters(id, counter_name => -1)
113
+ end
114
+ end
115
+ end