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.
- data/CHANGELOG +5518 -76
- data/README.rdoc +222 -0
- data/examples/performance.rb +162 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record/aggregations.rb +192 -80
- data/lib/active_record/association_preload.rb +403 -0
- data/lib/active_record/associations/association_collection.rb +545 -53
- data/lib/active_record/associations/association_proxy.rb +295 -0
- data/lib/active_record/associations/belongs_to_association.rb +91 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +78 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +127 -36
- data/lib/active_record/associations/has_many_association.rb +108 -84
- data/lib/active_record/associations/has_many_through_association.rb +116 -0
- data/lib/active_record/associations/has_one_association.rb +143 -0
- data/lib/active_record/associations/has_one_through_association.rb +40 -0
- data/lib/active_record/associations/through_association_scope.rb +154 -0
- data/lib/active_record/associations.rb +2086 -368
- data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
- data/lib/active_record/attribute_methods/dirty.rb +95 -0
- data/lib/active_record/attribute_methods/primary_key.rb +50 -0
- data/lib/active_record/attribute_methods/query.rb +39 -0
- data/lib/active_record/attribute_methods/read.rb +116 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -0
- data/lib/active_record/attribute_methods/write.rb +37 -0
- data/lib/active_record/attribute_methods.rb +60 -0
- data/lib/active_record/autosave_association.rb +369 -0
- data/lib/active_record/base.rb +1603 -721
- data/lib/active_record/callbacks.rb +176 -225
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +365 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +329 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +543 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +165 -279
- data/lib/active_record/connection_adapters/mysql_adapter.rb +594 -82
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +988 -135
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +365 -71
- data/lib/active_record/counter_cache.rb +115 -0
- data/lib/active_record/dynamic_finder_match.rb +53 -0
- data/lib/active_record/dynamic_scope_match.rb +32 -0
- data/lib/active_record/errors.rb +172 -0
- data/lib/active_record/fixtures.rb +941 -105
- data/lib/active_record/locale/en.yml +40 -0
- data/lib/active_record/locking/optimistic.rb +172 -0
- data/lib/active_record/locking/pessimistic.rb +55 -0
- data/lib/active_record/log_subscriber.rb +48 -0
- data/lib/active_record/migration.rb +617 -0
- data/lib/active_record/named_scope.rb +138 -0
- data/lib/active_record/nested_attributes.rb +417 -0
- data/lib/active_record/observer.rb +105 -36
- data/lib/active_record/persistence.rb +291 -0
- data/lib/active_record/query_cache.rb +36 -0
- data/lib/active_record/railtie.rb +91 -0
- data/lib/active_record/railties/controller_runtime.rb +38 -0
- data/lib/active_record/railties/databases.rake +512 -0
- data/lib/active_record/reflection.rb +364 -87
- data/lib/active_record/relation/batches.rb +89 -0
- data/lib/active_record/relation/calculations.rb +286 -0
- data/lib/active_record/relation/finder_methods.rb +355 -0
- data/lib/active_record/relation/predicate_builder.rb +41 -0
- data/lib/active_record/relation/query_methods.rb +261 -0
- data/lib/active_record/relation/spawn_methods.rb +112 -0
- data/lib/active_record/relation.rb +393 -0
- data/lib/active_record/schema.rb +59 -0
- data/lib/active_record/schema_dumper.rb +195 -0
- data/lib/active_record/serialization.rb +60 -0
- data/lib/active_record/serializers/xml_serializer.rb +244 -0
- data/lib/active_record/session_store.rb +340 -0
- data/lib/active_record/test_case.rb +67 -0
- data/lib/active_record/timestamp.rb +88 -0
- data/lib/active_record/transactions.rb +329 -75
- data/lib/active_record/validations/associated.rb +48 -0
- data/lib/active_record/validations/uniqueness.rb +185 -0
- data/lib/active_record/validations.rb +58 -179
- data/lib/active_record/version.rb +9 -0
- data/lib/active_record.rb +100 -24
- data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
- data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
- data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
- data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
- data/lib/rails/generators/active_record.rb +27 -0
- metadata +216 -158
- data/README +0 -361
- data/RUNNING_UNIT_TESTS +0 -36
- data/dev-utils/eval_debugger.rb +0 -9
- data/examples/associations.rb +0 -87
- data/examples/shared_setup.rb +0 -15
- data/examples/validation.rb +0 -88
- data/install.rb +0 -60
- data/lib/active_record/deprecated_associations.rb +0 -70
- data/lib/active_record/support/class_attribute_accessors.rb +0 -43
- data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
- data/lib/active_record/support/clean_logger.rb +0 -10
- data/lib/active_record/support/inflector.rb +0 -70
- data/lib/active_record/vendor/mysql.rb +0 -1117
- data/lib/active_record/vendor/simple.rb +0 -702
- data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
- data/lib/active_record/wrappings.rb +0 -59
- data/rakefile +0 -122
- data/test/abstract_unit.rb +0 -16
- data/test/aggregations_test.rb +0 -34
- data/test/all.sh +0 -8
- data/test/associations_test.rb +0 -477
- data/test/base_test.rb +0 -513
- data/test/class_inheritable_attributes_test.rb +0 -33
- data/test/connections/native_mysql/connection.rb +0 -24
- data/test/connections/native_postgresql/connection.rb +0 -24
- data/test/connections/native_sqlite/connection.rb +0 -24
- data/test/deprecated_associations_test.rb +0 -336
- data/test/finder_test.rb +0 -67
- data/test/fixtures/accounts/signals37 +0 -3
- data/test/fixtures/accounts/unknown +0 -2
- data/test/fixtures/auto_id.rb +0 -4
- data/test/fixtures/column_name.rb +0 -3
- data/test/fixtures/companies/first_client +0 -6
- data/test/fixtures/companies/first_firm +0 -4
- data/test/fixtures/companies/second_client +0 -6
- data/test/fixtures/company.rb +0 -37
- data/test/fixtures/company_in_module.rb +0 -33
- data/test/fixtures/course.rb +0 -3
- data/test/fixtures/courses/java +0 -2
- data/test/fixtures/courses/ruby +0 -2
- data/test/fixtures/customer.rb +0 -30
- data/test/fixtures/customers/david +0 -6
- data/test/fixtures/db_definitions/mysql.sql +0 -96
- data/test/fixtures/db_definitions/mysql2.sql +0 -4
- data/test/fixtures/db_definitions/postgresql.sql +0 -113
- data/test/fixtures/db_definitions/postgresql2.sql +0 -4
- data/test/fixtures/db_definitions/sqlite.sql +0 -85
- data/test/fixtures/db_definitions/sqlite2.sql +0 -4
- data/test/fixtures/default.rb +0 -2
- data/test/fixtures/developer.rb +0 -8
- data/test/fixtures/developers/david +0 -2
- data/test/fixtures/developers/jamis +0 -2
- data/test/fixtures/developers_projects/david_action_controller +0 -2
- data/test/fixtures/developers_projects/david_active_record +0 -2
- data/test/fixtures/developers_projects/jamis_active_record +0 -2
- data/test/fixtures/entrant.rb +0 -3
- data/test/fixtures/entrants/first +0 -3
- data/test/fixtures/entrants/second +0 -3
- data/test/fixtures/entrants/third +0 -3
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
- data/test/fixtures/movie.rb +0 -5
- data/test/fixtures/movies/first +0 -2
- data/test/fixtures/movies/second +0 -2
- data/test/fixtures/project.rb +0 -3
- data/test/fixtures/projects/action_controller +0 -2
- data/test/fixtures/projects/active_record +0 -2
- data/test/fixtures/reply.rb +0 -21
- data/test/fixtures/subscriber.rb +0 -5
- data/test/fixtures/subscribers/first +0 -2
- data/test/fixtures/subscribers/second +0 -2
- data/test/fixtures/topic.rb +0 -20
- data/test/fixtures/topics/first +0 -9
- data/test/fixtures/topics/second +0 -8
- data/test/fixtures_test.rb +0 -20
- data/test/inflector_test.rb +0 -104
- data/test/inheritance_test.rb +0 -125
- data/test/lifecycle_test.rb +0 -110
- data/test/modules_test.rb +0 -21
- data/test/multiple_db_test.rb +0 -46
- data/test/pk_test.rb +0 -57
- data/test/reflection_test.rb +0 -78
- data/test/thread_safety_test.rb +0 -33
- data/test/transactions_test.rb +0 -83
- data/test/unconnected_test.rb +0 -24
- 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
|
-
|
7
|
-
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
46
|
+
def <=>(version_string)
|
47
|
+
@version <=> version_string.split('.').map { |v| v.to_i }
|
48
|
+
end
|
49
|
+
end
|
23
50
|
|
24
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
67
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
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
|
-
|
74
|
-
|
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
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
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
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
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
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
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
|