activerecord 1.15.6 → 2.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 +2454 -34
- data/README +1 -1
- data/RUNNING_UNIT_TESTS +3 -34
- data/Rakefile +98 -77
- data/install.rb +1 -1
- data/lib/active_record.rb +13 -22
- data/lib/active_record/aggregations.rb +38 -49
- data/lib/active_record/associations.rb +452 -333
- data/lib/active_record/associations/association_collection.rb +66 -20
- data/lib/active_record/associations/association_proxy.rb +9 -8
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -51
- data/lib/active_record/associations/has_many_association.rb +21 -57
- data/lib/active_record/associations/has_many_through_association.rb +38 -18
- data/lib/active_record/associations/has_one_association.rb +30 -14
- data/lib/active_record/attribute_methods.rb +253 -0
- data/lib/active_record/base.rb +719 -494
- data/lib/active_record/calculations.rb +62 -63
- data/lib/active_record/callbacks.rb +57 -83
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +38 -9
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +56 -15
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -12
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +191 -62
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +37 -34
- data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -17
- data/lib/active_record/connection_adapters/mysql_adapter.rb +119 -37
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +473 -210
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +91 -107
- data/lib/active_record/fixtures.rb +503 -113
- data/lib/active_record/locking/optimistic.rb +72 -34
- data/lib/active_record/migration.rb +80 -57
- data/lib/active_record/observer.rb +13 -10
- data/lib/active_record/query_cache.rb +16 -57
- data/lib/active_record/reflection.rb +35 -38
- data/lib/active_record/schema.rb +5 -5
- data/lib/active_record/schema_dumper.rb +35 -13
- data/lib/active_record/serialization.rb +98 -0
- data/lib/active_record/serializers/json_serializer.rb +71 -0
- data/lib/active_record/{xml_serialization.rb → serializers/xml_serializer.rb} +90 -83
- data/lib/active_record/timestamp.rb +20 -21
- data/lib/active_record/transactions.rb +39 -43
- data/lib/active_record/validations.rb +256 -107
- data/lib/active_record/version.rb +3 -3
- data/lib/activerecord.rb +1 -0
- data/test/aaa_create_tables_test.rb +15 -2
- data/test/abstract_unit.rb +24 -17
- data/test/active_schema_test_mysql.rb +20 -8
- data/test/adapter_test.rb +23 -5
- data/test/adapter_test_sqlserver.rb +15 -1
- data/test/aggregations_test.rb +16 -1
- data/test/all.sh +2 -2
- data/test/associations/ar_joins_test.rb +0 -0
- data/test/associations/callbacks_test.rb +51 -30
- data/test/associations/cascaded_eager_loading_test.rb +1 -29
- data/test/associations/eager_singularization_test.rb +145 -0
- data/test/associations/eager_test.rb +42 -6
- data/test/associations/extension_test.rb +6 -1
- data/test/associations/inner_join_association_test.rb +88 -0
- data/test/associations/join_model_test.rb +47 -16
- data/test/associations_test.rb +449 -226
- data/test/attribute_methods_test.rb +97 -0
- data/test/base_test.rb +251 -105
- data/test/binary_test.rb +22 -27
- data/test/calculations_test.rb +37 -5
- data/test/callbacks_test.rb +23 -0
- data/test/connection_test_firebird.rb +2 -2
- data/test/connection_test_mysql.rb +30 -0
- data/test/connections/native_mysql/connection.rb +3 -0
- data/test/connections/native_sqlite/connection.rb +5 -14
- data/test/connections/native_sqlite3/connection.rb +5 -14
- data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
- data/test/{copy_table_sqlite.rb → copy_table_test_sqlite.rb} +8 -3
- data/test/datatype_test_postgresql.rb +178 -27
- data/test/{empty_date_time_test.rb → date_time_test.rb} +13 -1
- data/test/defaults_test.rb +8 -1
- data/test/deprecated_finder_test.rb +7 -128
- data/test/finder_test.rb +192 -54
- data/test/fixtures/all/developers.yml +0 -0
- data/test/fixtures/all/people.csv +0 -0
- data/test/fixtures/all/tasks.yml +0 -0
- data/test/fixtures/author.rb +12 -5
- data/test/fixtures/binaries.yml +130 -435
- data/test/fixtures/category.rb +6 -0
- data/test/fixtures/company.rb +8 -1
- data/test/fixtures/computer.rb +1 -0
- data/test/fixtures/contact.rb +16 -0
- data/test/fixtures/customer.rb +2 -2
- data/test/fixtures/db_definitions/db2.drop.sql +1 -0
- data/test/fixtures/db_definitions/db2.sql +4 -0
- data/test/fixtures/db_definitions/firebird.drop.sql +3 -1
- data/test/fixtures/db_definitions/firebird.sql +6 -0
- data/test/fixtures/db_definitions/frontbase.drop.sql +1 -0
- data/test/fixtures/db_definitions/frontbase.sql +5 -0
- data/test/fixtures/db_definitions/openbase.sql +41 -25
- data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
- data/test/fixtures/db_definitions/oracle.sql +5 -0
- data/test/fixtures/db_definitions/postgresql.drop.sql +7 -0
- data/test/fixtures/db_definitions/postgresql.sql +87 -58
- data/test/fixtures/db_definitions/postgresql2.sql +1 -2
- data/test/fixtures/db_definitions/schema.rb +280 -0
- data/test/fixtures/db_definitions/schema2.rb +11 -0
- data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
- data/test/fixtures/db_definitions/sqlite.sql +4 -0
- data/test/fixtures/db_definitions/sybase.drop.sql +1 -0
- data/test/fixtures/db_definitions/sybase.sql +4 -0
- data/test/fixtures/developer.rb +10 -0
- data/test/fixtures/example.log +1 -0
- data/test/fixtures/flowers.jpg +0 -0
- data/test/fixtures/item.rb +7 -0
- data/test/fixtures/items.yml +4 -0
- data/test/fixtures/joke.rb +0 -3
- data/test/fixtures/matey.rb +4 -0
- data/test/fixtures/mateys.yml +4 -0
- data/test/fixtures/minimalistic.rb +2 -0
- data/test/fixtures/minimalistics.yml +2 -0
- data/test/fixtures/mixins.yml +2 -100
- data/test/fixtures/parrot.rb +13 -0
- data/test/fixtures/parrots.yml +27 -0
- data/test/fixtures/parrots_pirates.yml +7 -0
- data/test/fixtures/pirate.rb +5 -0
- data/test/fixtures/pirates.yml +9 -0
- data/test/fixtures/post.rb +1 -0
- data/test/fixtures/project.rb +3 -2
- data/test/fixtures/reserved_words/distinct.yml +5 -0
- data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
- data/test/fixtures/reserved_words/group.yml +14 -0
- data/test/fixtures/reserved_words/select.yml +8 -0
- data/test/fixtures/reserved_words/values.yml +7 -0
- data/test/fixtures/ship.rb +3 -0
- data/test/fixtures/ships.yml +5 -0
- data/test/fixtures/tagging.rb +4 -0
- data/test/fixtures/taggings.yml +8 -1
- data/test/fixtures/topic.rb +13 -1
- data/test/fixtures/treasure.rb +4 -0
- data/test/fixtures/treasures.yml +10 -0
- data/test/fixtures_test.rb +205 -24
- data/test/inheritance_test.rb +7 -1
- data/test/json_serialization_test.rb +180 -0
- data/test/lifecycle_test.rb +1 -1
- data/test/locking_test.rb +85 -2
- data/test/migration_test.rb +206 -40
- data/test/mixin_test.rb +13 -515
- data/test/pk_test.rb +3 -6
- data/test/query_cache_test.rb +104 -0
- data/test/reflection_test.rb +16 -0
- data/test/reserved_word_test_mysql.rb +177 -0
- data/test/schema_dumper_test.rb +38 -3
- data/test/serialization_test.rb +47 -0
- data/test/transactions_test.rb +74 -23
- data/test/unconnected_test.rb +1 -1
- data/test/validations_test.rb +322 -32
- data/test/xml_serialization_test.rb +121 -44
- metadata +48 -41
- data/examples/associations.rb +0 -87
- data/examples/shared_setup.rb +0 -15
- data/examples/validation.rb +0 -85
- data/lib/active_record/acts/list.rb +0 -256
- data/lib/active_record/acts/nested_set.rb +0 -211
- data/lib/active_record/acts/tree.rb +0 -96
- data/lib/active_record/connection_adapters/db2_adapter.rb +0 -228
- data/lib/active_record/connection_adapters/firebird_adapter.rb +0 -728
- data/lib/active_record/connection_adapters/frontbase_adapter.rb +0 -861
- data/lib/active_record/connection_adapters/openbase_adapter.rb +0 -350
- data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -690
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +0 -591
- data/lib/active_record/connection_adapters/sybase_adapter.rb +0 -662
- data/lib/active_record/deprecated_associations.rb +0 -104
- data/lib/active_record/deprecated_finders.rb +0 -44
- data/lib/active_record/vendor/simple.rb +0 -693
- data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
- data/lib/active_record/wrappings.rb +0 -58
- data/test/connections/native_sqlserver/connection.rb +0 -23
- data/test/connections/native_sqlserver_odbc/connection.rb +0 -25
- data/test/deprecated_associations_test.rb +0 -396
- data/test/fixtures/db_definitions/mysql.drop.sql +0 -32
- data/test/fixtures/db_definitions/mysql.sql +0 -234
- data/test/fixtures/db_definitions/mysql2.drop.sql +0 -2
- data/test/fixtures/db_definitions/mysql2.sql +0 -5
- data/test/fixtures/db_definitions/sqlserver.drop.sql +0 -34
- data/test/fixtures/db_definitions/sqlserver.sql +0 -243
- data/test/fixtures/db_definitions/sqlserver2.drop.sql +0 -2
- data/test/fixtures/db_definitions/sqlserver2.sql +0 -5
- data/test/fixtures/mixin.rb +0 -63
- data/test/mixin_nested_set_test.rb +0 -196
@@ -89,10 +89,23 @@ module ActiveRecord
|
|
89
89
|
|
90
90
|
# Clears the cache which maps classes
|
91
91
|
def clear_reloadable_connections!
|
92
|
-
@@
|
93
|
-
|
94
|
-
|
95
|
-
|
92
|
+
if @@allow_concurrency
|
93
|
+
# With concurrent connections @@active_connections is
|
94
|
+
# a hash keyed by thread id.
|
95
|
+
@@active_connections.each do |thread_id, conns|
|
96
|
+
conns.each do |name, conn|
|
97
|
+
if conn.requires_reloading?
|
98
|
+
conn.disconnect!
|
99
|
+
@@active_connections[thread_id].delete(name)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
else
|
104
|
+
@@active_connections.each do |name, conn|
|
105
|
+
if conn.requires_reloading?
|
106
|
+
conn.disconnect!
|
107
|
+
@@active_connections.delete(name)
|
108
|
+
end
|
96
109
|
end
|
97
110
|
end
|
98
111
|
end
|
@@ -206,15 +219,31 @@ module ActiveRecord
|
|
206
219
|
else
|
207
220
|
spec = spec.symbolize_keys
|
208
221
|
unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end
|
222
|
+
|
223
|
+
begin
|
224
|
+
require 'rubygems'
|
225
|
+
gem "activerecord-#{spec[:adapter]}-adapter"
|
226
|
+
require "active_record/connection_adapters/#{spec[:adapter]}_adapter"
|
227
|
+
rescue LoadError
|
228
|
+
begin
|
229
|
+
require "active_record/connection_adapters/#{spec[:adapter]}_adapter"
|
230
|
+
rescue LoadError
|
231
|
+
raise "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{$!})"
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
209
235
|
adapter_method = "#{spec[:adapter]}_connection"
|
210
|
-
|
236
|
+
if !respond_to?(adapter_method)
|
237
|
+
raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter"
|
238
|
+
end
|
239
|
+
|
211
240
|
remove_connection
|
212
241
|
establish_connection(ConnectionSpecification.new(spec, adapter_method))
|
213
242
|
end
|
214
243
|
end
|
215
244
|
|
216
245
|
# Locate the connection of the nearest super class. This can be an
|
217
|
-
# active or defined
|
246
|
+
# active or defined connection: if it is the latter, it will be
|
218
247
|
# opened and set as the active connection for the class it was defined
|
219
248
|
# for (not necessarily the current class).
|
220
249
|
def self.retrieve_connection #:nodoc:
|
@@ -235,15 +264,15 @@ module ActiveRecord
|
|
235
264
|
conn or raise ConnectionNotEstablished
|
236
265
|
end
|
237
266
|
|
238
|
-
# Returns true if a connection that's accessible to this class
|
267
|
+
# Returns true if a connection that's accessible to this class has already been opened.
|
239
268
|
def self.connected?
|
240
269
|
active_connections[active_connection_name] ? true : false
|
241
270
|
end
|
242
271
|
|
243
272
|
# Remove the connection for this class. This will close the active
|
244
273
|
# connection and the defined connection (if they exist). The result
|
245
|
-
# can be used as argument for establish_connection, for
|
246
|
-
# re-establishing
|
274
|
+
# can be used as an argument for establish_connection, for easily
|
275
|
+
# re-establishing the connection.
|
247
276
|
def self.remove_connection(klass=self)
|
248
277
|
spec = @@defined_connections[klass.name]
|
249
278
|
konn = active_connections[klass.name]
|
@@ -10,21 +10,28 @@ module ActiveRecord
|
|
10
10
|
# Returns a record hash with the column names as keys and column values
|
11
11
|
# as values.
|
12
12
|
def select_one(sql, name = nil)
|
13
|
-
result =
|
13
|
+
result = select_all(sql, name)
|
14
14
|
result.first if result
|
15
15
|
end
|
16
16
|
|
17
17
|
# Returns a single value from a record
|
18
18
|
def select_value(sql, name = nil)
|
19
|
-
result = select_one(sql, name)
|
20
|
-
|
19
|
+
if result = select_one(sql, name)
|
20
|
+
result.values.first
|
21
|
+
end
|
21
22
|
end
|
22
23
|
|
23
24
|
# Returns an array of the values of the first column in a select:
|
24
25
|
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
|
25
26
|
def select_values(sql, name = nil)
|
26
|
-
result =
|
27
|
-
result.map{ |v| v
|
27
|
+
result = select_rows(sql, name)
|
28
|
+
result.map { |v| v[0] }
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns an array of arrays containing the field values.
|
32
|
+
# Order is the same as that returned by #columns.
|
33
|
+
def select_rows(sql, name = nil)
|
34
|
+
raise NotImplementedError, "select_rows is an abstract method"
|
28
35
|
end
|
29
36
|
|
30
37
|
# Executes the SQL statement in the context of this connection.
|
@@ -34,17 +41,17 @@ module ActiveRecord
|
|
34
41
|
|
35
42
|
# Returns the last auto-generated ID from the affected table.
|
36
43
|
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
37
|
-
|
44
|
+
insert_sql(sql, name, pk, id_value, sequence_name)
|
38
45
|
end
|
39
46
|
|
40
47
|
# Executes the update statement and returns the number of rows affected.
|
41
48
|
def update(sql, name = nil)
|
42
|
-
|
49
|
+
update_sql(sql, name)
|
43
50
|
end
|
44
51
|
|
45
52
|
# Executes the delete statement and returns the number of rows affected.
|
46
53
|
def delete(sql, name = nil)
|
47
|
-
|
54
|
+
delete_sql(sql, name)
|
48
55
|
end
|
49
56
|
|
50
57
|
# Wrap a block in a transaction. Returns result of block.
|
@@ -53,7 +60,7 @@ module ActiveRecord
|
|
53
60
|
begin
|
54
61
|
if block_given?
|
55
62
|
if start_db_transaction
|
56
|
-
begin_db_transaction
|
63
|
+
begin_db_transaction
|
57
64
|
transaction_open = true
|
58
65
|
end
|
59
66
|
yield
|
@@ -63,10 +70,17 @@ module ActiveRecord
|
|
63
70
|
transaction_open = false
|
64
71
|
rollback_db_transaction
|
65
72
|
end
|
66
|
-
raise
|
73
|
+
raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback
|
67
74
|
end
|
68
75
|
ensure
|
69
|
-
|
76
|
+
if transaction_open
|
77
|
+
begin
|
78
|
+
commit_db_transaction
|
79
|
+
rescue Exception => database_transaction_rollback
|
80
|
+
rollback_db_transaction
|
81
|
+
raise
|
82
|
+
end
|
83
|
+
end
|
70
84
|
end
|
71
85
|
|
72
86
|
# Begins the transaction (and turns off auto-committing).
|
@@ -84,7 +98,7 @@ module ActiveRecord
|
|
84
98
|
add_limit_offset!(sql, options) if options
|
85
99
|
end
|
86
100
|
|
87
|
-
# Appends +LIMIT+ and +OFFSET+ options to
|
101
|
+
# Appends +LIMIT+ and +OFFSET+ options to an SQL statement.
|
88
102
|
# This method *modifies* the +sql+ parameter.
|
89
103
|
# ===== Examples
|
90
104
|
# add_limit_offset!('SELECT * FROM suppliers', {:limit => 10, :offset => 50})
|
@@ -99,14 +113,15 @@ module ActiveRecord
|
|
99
113
|
end
|
100
114
|
end
|
101
115
|
|
102
|
-
# Appends a locking clause to
|
116
|
+
# Appends a locking clause to an SQL statement.
|
117
|
+
# This method *modifies* the +sql+ parameter.
|
103
118
|
# # SELECT * FROM suppliers FOR UPDATE
|
104
119
|
# add_lock! 'SELECT * FROM suppliers', :lock => true
|
105
120
|
# add_lock! 'SELECT * FROM suppliers', :lock => ' FOR UPDATE'
|
106
121
|
def add_lock!(sql, options)
|
107
122
|
case lock = options[:lock]
|
108
|
-
when true
|
109
|
-
when String
|
123
|
+
when true; sql << ' FOR UPDATE'
|
124
|
+
when String; sql << " #{lock}"
|
110
125
|
end
|
111
126
|
end
|
112
127
|
|
@@ -119,12 +134,38 @@ module ActiveRecord
|
|
119
134
|
# Do nothing by default. Implement for PostgreSQL, Oracle, ...
|
120
135
|
end
|
121
136
|
|
137
|
+
# Inserts the given fixture into the table. Overridden in adapters that require
|
138
|
+
# something beyond a simple insert (eg. Oracle).
|
139
|
+
def insert_fixture(fixture, table_name)
|
140
|
+
execute "INSERT INTO #{quote_table_name(table_name)} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
|
141
|
+
end
|
142
|
+
|
143
|
+
def empty_insert_statement(table_name)
|
144
|
+
"INSERT INTO #{quote_table_name(table_name)} VALUES(DEFAULT)"
|
145
|
+
end
|
146
|
+
|
122
147
|
protected
|
123
148
|
# Returns an array of record hashes with the column names as keys and
|
124
149
|
# column values as values.
|
125
150
|
def select(sql, name = nil)
|
126
151
|
raise NotImplementedError, "select is an abstract method"
|
127
152
|
end
|
153
|
+
|
154
|
+
# Returns the last auto-generated ID from the affected table.
|
155
|
+
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
156
|
+
execute(sql, name)
|
157
|
+
id_value
|
158
|
+
end
|
159
|
+
|
160
|
+
# Executes the update statement and returns the number of rows affected.
|
161
|
+
def update_sql(sql, name = nil)
|
162
|
+
execute(sql, name)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Executes the delete statement and returns the number of rows affected.
|
166
|
+
def delete_sql(sql, name = nil)
|
167
|
+
update_sql(sql, name)
|
168
|
+
end
|
128
169
|
end
|
129
170
|
end
|
130
171
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters # :nodoc:
|
3
|
+
module QueryCache
|
4
|
+
class << self
|
5
|
+
def included(base)
|
6
|
+
base.class_eval do
|
7
|
+
attr_accessor :query_cache_enabled
|
8
|
+
alias_method_chain :columns, :query_cache
|
9
|
+
alias_method_chain :select_all, :query_cache
|
10
|
+
end
|
11
|
+
|
12
|
+
dirties_query_cache base, :insert, :update, :delete
|
13
|
+
end
|
14
|
+
|
15
|
+
def dirties_query_cache(base, *method_names)
|
16
|
+
method_names.each do |method_name|
|
17
|
+
base.class_eval <<-end_code, __FILE__, __LINE__
|
18
|
+
def #{method_name}_with_query_dirty(*args)
|
19
|
+
clear_query_cache if @query_cache_enabled
|
20
|
+
#{method_name}_without_query_dirty(*args)
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_method_chain :#{method_name}, :query_dirty
|
24
|
+
end_code
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Enable the query cache within the block.
|
30
|
+
def cache
|
31
|
+
old, @query_cache_enabled = @query_cache_enabled, true
|
32
|
+
@query_cache ||= {}
|
33
|
+
yield
|
34
|
+
ensure
|
35
|
+
clear_query_cache
|
36
|
+
@query_cache_enabled = old
|
37
|
+
end
|
38
|
+
|
39
|
+
# Disable the query cache within the block.
|
40
|
+
def uncached
|
41
|
+
old, @query_cache_enabled = @query_cache_enabled, false
|
42
|
+
yield
|
43
|
+
ensure
|
44
|
+
@query_cache_enabled = old
|
45
|
+
end
|
46
|
+
|
47
|
+
def clear_query_cache
|
48
|
+
@query_cache.clear if @query_cache
|
49
|
+
end
|
50
|
+
|
51
|
+
def select_all_with_query_cache(*args)
|
52
|
+
if @query_cache_enabled
|
53
|
+
cache_sql(args.first) { select_all_without_query_cache(*args) }
|
54
|
+
else
|
55
|
+
select_all_without_query_cache(*args)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def columns_with_query_cache(*args)
|
60
|
+
if @query_cache_enabled
|
61
|
+
@query_cache["SHOW FIELDS FROM #{args.first}"] ||= columns_without_query_cache(*args)
|
62
|
+
else
|
63
|
+
columns_without_query_cache(*args)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
def cache_sql(sql)
|
69
|
+
result =
|
70
|
+
if @query_cache.has_key?(sql)
|
71
|
+
log_info(sql, "CACHE", 0.0)
|
72
|
+
@query_cache[sql]
|
73
|
+
else
|
74
|
+
@query_cache[sql] = yield
|
75
|
+
end
|
76
|
+
|
77
|
+
if Array === result
|
78
|
+
result.collect { |row| row.dup }
|
79
|
+
else
|
80
|
+
result.duplicable? ? result.dup : result
|
81
|
+
end
|
82
|
+
rescue TypeError
|
83
|
+
result
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -11,12 +11,12 @@ module ActiveRecord
|
|
11
11
|
when String, ActiveSupport::Multibyte::Chars
|
12
12
|
value = value.to_s
|
13
13
|
if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
|
14
|
-
"'#{quote_string(column.class.string_to_binary(value))}'" # ' (for ruby-mode)
|
14
|
+
"#{quoted_string_prefix}'#{quote_string(column.class.string_to_binary(value))}'" # ' (for ruby-mode)
|
15
15
|
elsif column && [:integer, :float].include?(column.type)
|
16
16
|
value = column.type == :integer ? value.to_i : value.to_f
|
17
17
|
value.to_s
|
18
18
|
else
|
19
|
-
"'#{quote_string(value)}'" # ' (for ruby-mode)
|
19
|
+
"#{quoted_string_prefix}'#{quote_string(value)}'" # ' (for ruby-mode)
|
20
20
|
end
|
21
21
|
when NilClass then "NULL"
|
22
22
|
when TrueClass then (column && column.type == :integer ? '1' : quoted_true)
|
@@ -24,9 +24,12 @@ module ActiveRecord
|
|
24
24
|
when Float, Fixnum, Bignum then value.to_s
|
25
25
|
# BigDecimals need to be output in a non-normalized form and quoted.
|
26
26
|
when BigDecimal then value.to_s('F')
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
else
|
28
|
+
if value.acts_like?(:date) || value.acts_like?(:time)
|
29
|
+
"'#{quoted_date(value)}'"
|
30
|
+
else
|
31
|
+
"#{quoted_string_prefix}'#{quote_string(value.to_yaml)}'"
|
32
|
+
end
|
30
33
|
end
|
31
34
|
end
|
32
35
|
|
@@ -36,22 +39,30 @@ module ActiveRecord
|
|
36
39
|
s.gsub(/\\/, '\&\&').gsub(/'/, "''") # ' (for ruby-mode)
|
37
40
|
end
|
38
41
|
|
39
|
-
#
|
40
|
-
|
41
|
-
|
42
|
-
|
42
|
+
# Quotes the column name. Defaults to no quoting.
|
43
|
+
def quote_column_name(column_name)
|
44
|
+
column_name
|
45
|
+
end
|
46
|
+
|
47
|
+
# Quotes the table name. Defaults to column name quoting.
|
48
|
+
def quote_table_name(table_name)
|
49
|
+
quote_column_name(table_name)
|
43
50
|
end
|
44
51
|
|
45
52
|
def quoted_true
|
46
53
|
"'t'"
|
47
54
|
end
|
48
|
-
|
55
|
+
|
49
56
|
def quoted_false
|
50
57
|
"'f'"
|
51
58
|
end
|
52
|
-
|
59
|
+
|
53
60
|
def quoted_date(value)
|
54
|
-
value.
|
61
|
+
value.to_s(:db)
|
62
|
+
end
|
63
|
+
|
64
|
+
def quoted_string_prefix
|
65
|
+
''
|
55
66
|
end
|
56
67
|
end
|
57
68
|
end
|
@@ -6,6 +6,11 @@ module ActiveRecord
|
|
6
6
|
module ConnectionAdapters #:nodoc:
|
7
7
|
# An abstract definition of a column in a table.
|
8
8
|
class Column
|
9
|
+
module Format
|
10
|
+
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
|
11
|
+
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
|
12
|
+
end
|
13
|
+
|
9
14
|
attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale
|
10
15
|
attr_accessor :primary
|
11
16
|
|
@@ -19,7 +24,7 @@ module ActiveRecord
|
|
19
24
|
@name, @sql_type, @null = name, sql_type, null
|
20
25
|
@limit, @precision, @scale = extract_limit(sql_type), extract_precision(sql_type), extract_scale(sql_type)
|
21
26
|
@type = simplified_type(sql_type)
|
22
|
-
@default =
|
27
|
+
@default = extract_default(default)
|
23
28
|
|
24
29
|
@primary = nil
|
25
30
|
end
|
@@ -92,70 +97,113 @@ module ActiveRecord
|
|
92
97
|
Base.human_attribute_name(@name)
|
93
98
|
end
|
94
99
|
|
95
|
-
|
96
|
-
|
97
|
-
value
|
100
|
+
def extract_default(default)
|
101
|
+
type_cast(default)
|
98
102
|
end
|
99
103
|
|
100
|
-
|
101
|
-
|
102
|
-
value
|
103
|
-
|
104
|
+
class << self
|
105
|
+
# Used to convert from Strings to BLOBs
|
106
|
+
def string_to_binary(value)
|
107
|
+
value
|
108
|
+
end
|
104
109
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
Date.new(date_array[0], date_array[1], date_array[2]) rescue nil
|
110
|
-
end
|
110
|
+
# Used to convert from BLOBs to Strings
|
111
|
+
def binary_to_string(value)
|
112
|
+
value
|
113
|
+
end
|
111
114
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
time_hash[:sec_fraction] = microseconds(time_hash)
|
116
|
-
time_array = time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction)
|
117
|
-
# treat 0000-00-00 00:00:00 as nil
|
118
|
-
Time.send(Base.default_timezone, *time_array) rescue DateTime.new(*time_array[0..5]) rescue nil
|
119
|
-
end
|
115
|
+
def string_to_date(string)
|
116
|
+
return string unless string.is_a?(String)
|
117
|
+
return nil if string.empty?
|
120
118
|
|
121
|
-
|
122
|
-
|
123
|
-
return nil if string.empty?
|
124
|
-
time_hash = Date._parse(string)
|
125
|
-
time_hash[:sec_fraction] = microseconds(time_hash)
|
126
|
-
# pad the resulting array with dummy date information
|
127
|
-
time_array = [2000, 1, 1]
|
128
|
-
time_array += time_hash.values_at(:hour, :min, :sec, :sec_fraction)
|
129
|
-
Time.send(Base.default_timezone, *time_array) rescue nil
|
130
|
-
end
|
119
|
+
fast_string_to_date(string) || fallback_string_to_date(string)
|
120
|
+
end
|
131
121
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
%w(true t 1).include?(value.to_s.downcase)
|
122
|
+
def string_to_time(string)
|
123
|
+
return string unless string.is_a?(String)
|
124
|
+
return nil if string.empty?
|
125
|
+
|
126
|
+
fast_string_to_time(string) || fallback_string_to_time(string)
|
138
127
|
end
|
139
|
-
end
|
140
128
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
value.to_d
|
147
|
-
else
|
148
|
-
value.to_s.to_d
|
129
|
+
def string_to_dummy_time(string)
|
130
|
+
return string unless string.is_a?(String)
|
131
|
+
return nil if string.empty?
|
132
|
+
|
133
|
+
string_to_time "2000-01-01 #{string}"
|
149
134
|
end
|
150
|
-
end
|
151
135
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
136
|
+
# convert something to a boolean
|
137
|
+
def value_to_boolean(value)
|
138
|
+
if value == true || value == false
|
139
|
+
value
|
140
|
+
else
|
141
|
+
%w(true t 1).include?(value.to_s.downcase)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# convert something to a BigDecimal
|
146
|
+
def value_to_decimal(value)
|
147
|
+
if value.is_a?(BigDecimal)
|
148
|
+
value
|
149
|
+
elsif value.respond_to?(:to_d)
|
150
|
+
value.to_d
|
151
|
+
else
|
152
|
+
value.to_s.to_d
|
153
|
+
end
|
157
154
|
end
|
158
155
|
|
156
|
+
protected
|
157
|
+
# '0.123456' -> 123456
|
158
|
+
# '1.123456' -> 123456
|
159
|
+
def microseconds(time)
|
160
|
+
((time[:sec_fraction].to_f % 1) * 1_000_000).to_i
|
161
|
+
end
|
162
|
+
|
163
|
+
def new_date(year, mon, mday)
|
164
|
+
if year && year != 0
|
165
|
+
Date.new(year, mon, mday) rescue nil
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def new_time(year, mon, mday, hour, min, sec, microsec)
|
170
|
+
# Treat 0000-00-00 00:00:00 as nil.
|
171
|
+
return nil if year.nil? || year == 0
|
172
|
+
|
173
|
+
Time.send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec)
|
174
|
+
# Over/underflow to DateTime
|
175
|
+
rescue ArgumentError, TypeError
|
176
|
+
zone_offset = Base.default_timezone == :local ? DateTime.local_offset : 0
|
177
|
+
DateTime.civil(year, mon, mday, hour, min, sec, zone_offset) rescue nil
|
178
|
+
end
|
179
|
+
|
180
|
+
def fast_string_to_date(string)
|
181
|
+
if string =~ Format::ISO_DATE
|
182
|
+
new_date $1.to_i, $2.to_i, $3.to_i
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Doesn't handle time zones.
|
187
|
+
def fast_string_to_time(string)
|
188
|
+
if string =~ Format::ISO_DATETIME
|
189
|
+
microsec = ($7.to_f * 1_000_000).to_i
|
190
|
+
new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def fallback_string_to_date(string)
|
195
|
+
new_date *ParseDate.parsedate(string)[0..2]
|
196
|
+
end
|
197
|
+
|
198
|
+
def fallback_string_to_time(string)
|
199
|
+
time_hash = Date._parse(string)
|
200
|
+
time_hash[:sec_fraction] = microseconds(time_hash)
|
201
|
+
|
202
|
+
new_time *time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
private
|
159
207
|
def extract_limit(sql_type)
|
160
208
|
$1.to_i if sql_type =~ /\((.*)\)/
|
161
209
|
end
|
@@ -223,7 +271,7 @@ module ActiveRecord
|
|
223
271
|
end
|
224
272
|
|
225
273
|
# Represents a SQL table in an abstract way.
|
226
|
-
# Columns are stored as ColumnDefinition in the #columns attribute.
|
274
|
+
# Columns are stored as a ColumnDefinition in the #columns attribute.
|
227
275
|
class TableDefinition
|
228
276
|
attr_accessor :columns
|
229
277
|
|
@@ -244,24 +292,29 @@ module ActiveRecord
|
|
244
292
|
end
|
245
293
|
|
246
294
|
# Instantiates a new column for the table.
|
247
|
-
# The +type+ parameter
|
295
|
+
# The +type+ parameter is normally one of the migrations native types,
|
296
|
+
# which is one of the following:
|
248
297
|
# <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
|
249
298
|
# <tt>:integer</tt>, <tt>:float</tt>, <tt>:decimal</tt>,
|
250
299
|
# <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
|
251
300
|
# <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>.
|
252
301
|
#
|
302
|
+
# You may use a type not in this list as long as it is supported by your
|
303
|
+
# database (for example, "polygon" in MySQL), but this will not be database
|
304
|
+
# agnostic and should usually be avoided.
|
305
|
+
#
|
253
306
|
# Available options are (none of these exists by default):
|
254
|
-
# * <tt>:limit</tt
|
307
|
+
# * <tt>:limit</tt> -
|
255
308
|
# Requests a maximum column length (<tt>:string</tt>, <tt>:text</tt>,
|
256
309
|
# <tt>:binary</tt> or <tt>:integer</tt> columns only)
|
257
|
-
# * <tt>:default</tt
|
310
|
+
# * <tt>:default</tt> -
|
258
311
|
# The column's default value. Use nil for NULL.
|
259
|
-
# * <tt>:null</tt
|
312
|
+
# * <tt>:null</tt> -
|
260
313
|
# Allows or disallows +NULL+ values in the column. This option could
|
261
314
|
# have been named <tt>:null_allowed</tt>.
|
262
|
-
# * <tt>:precision</tt
|
315
|
+
# * <tt>:precision</tt> -
|
263
316
|
# Specifies the precision for a <tt>:decimal</tt> column.
|
264
|
-
# * <tt>:scale</tt
|
317
|
+
# * <tt>:scale</tt> -
|
265
318
|
# Specifies the scale for a <tt>:decimal</tt> column.
|
266
319
|
#
|
267
320
|
# Please be aware of different RDBMS implementations behavior with
|
@@ -295,7 +348,7 @@ module ActiveRecord
|
|
295
348
|
#
|
296
349
|
# This method returns <tt>self</tt>.
|
297
350
|
#
|
298
|
-
#
|
351
|
+
# == Examples
|
299
352
|
# # Assuming td is an instance of TableDefinition
|
300
353
|
# td.column(:granted, :boolean)
|
301
354
|
# #=> granted BOOLEAN
|
@@ -316,6 +369,52 @@ module ActiveRecord
|
|
316
369
|
# # probably wouldn't hurt to include it.
|
317
370
|
# def.column(:huge_integer, :decimal, :precision => 30)
|
318
371
|
# #=> huge_integer DECIMAL(30)
|
372
|
+
#
|
373
|
+
# == Short-hand examples
|
374
|
+
#
|
375
|
+
# Instead of calling column directly, you can also work with the short-hand definitions for the default types.
|
376
|
+
# They use the type as the method name instead of as a parameter and allow for multiple columns to be defined
|
377
|
+
# in a single statement.
|
378
|
+
#
|
379
|
+
# What can be written like this with the regular calls to column:
|
380
|
+
#
|
381
|
+
# create_table "products", :force => true do |t|
|
382
|
+
# t.column "shop_id", :integer
|
383
|
+
# t.column "creator_id", :integer
|
384
|
+
# t.column "name", :string, :default => "Untitled"
|
385
|
+
# t.column "value", :string, :default => "Untitled"
|
386
|
+
# t.column "created_at", :datetime
|
387
|
+
# t.column "updated_at", :datetime
|
388
|
+
# end
|
389
|
+
#
|
390
|
+
# Can also be written as follows using the short-hand:
|
391
|
+
#
|
392
|
+
# create_table :products do |t|
|
393
|
+
# t.integer :shop_id, :creator_id
|
394
|
+
# t.string :name, :value, :default => "Untitled"
|
395
|
+
# t.timestamps
|
396
|
+
# end
|
397
|
+
#
|
398
|
+
# There's a short-hand method for each of the type values declared at the top. And then there's
|
399
|
+
# TableDefinition#timestamps that'll add created_at and updated_at as datetimes.
|
400
|
+
#
|
401
|
+
# TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type
|
402
|
+
# column if the :polymorphic option is supplied. If :polymorphic is a hash of options, these will be
|
403
|
+
# used when creating the _type column. So what can be written like this:
|
404
|
+
#
|
405
|
+
# create_table :taggings do |t|
|
406
|
+
# t.integer :tag_id, :tagger_id, :taggable_id
|
407
|
+
# t.string :tagger_type
|
408
|
+
# t.string :taggable_type, :default => 'Photo'
|
409
|
+
# end
|
410
|
+
#
|
411
|
+
# Can also be written as follows using references:
|
412
|
+
#
|
413
|
+
# create_table :taggings do |t|
|
414
|
+
# t.references :tag
|
415
|
+
# t.references :tagger, :polymorphic => true
|
416
|
+
# t.references :taggable, :polymorphic => { :default => 'Photo' }
|
417
|
+
# end
|
319
418
|
def column(name, type, options = {})
|
320
419
|
column = self[name] || ColumnDefinition.new(@base, name, type)
|
321
420
|
column.limit = options[:limit] || native[type.to_sym][:limit] if options[:limit] or native[type.to_sym]
|
@@ -327,8 +426,38 @@ module ActiveRecord
|
|
327
426
|
self
|
328
427
|
end
|
329
428
|
|
429
|
+
%w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
|
430
|
+
class_eval <<-EOV
|
431
|
+
def #{column_type}(*args)
|
432
|
+
options = args.extract_options!
|
433
|
+
column_names = args
|
434
|
+
|
435
|
+
column_names.each { |name| column(name, '#{column_type}', options) }
|
436
|
+
end
|
437
|
+
EOV
|
438
|
+
end
|
439
|
+
|
440
|
+
# Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
|
441
|
+
# <tt>:updated_at</tt> to the table.
|
442
|
+
def timestamps
|
443
|
+
column(:created_at, :datetime)
|
444
|
+
column(:updated_at, :datetime)
|
445
|
+
end
|
446
|
+
|
447
|
+
def references(*args)
|
448
|
+
options = args.extract_options!
|
449
|
+
polymorphic = options.delete(:polymorphic)
|
450
|
+
args.each do |col|
|
451
|
+
column("#{col}_id", :integer, options)
|
452
|
+
unless polymorphic.nil?
|
453
|
+
column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : {})
|
454
|
+
end
|
455
|
+
end
|
456
|
+
end
|
457
|
+
alias :belongs_to :references
|
458
|
+
|
330
459
|
# Returns a String whose contents are the column definitions
|
331
|
-
# concatenated together. This string can then be
|
460
|
+
# concatenated together. This string can then be prepended and appended to
|
332
461
|
# to generate the final SQL to create the table.
|
333
462
|
def to_sql
|
334
463
|
@columns * ', '
|