datamapper 0.2.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +10 -2
- data/environment.rb +0 -3
- data/lib/data_mapper.rb +2 -0
- data/lib/data_mapper/adapters/abstract_adapter.rb +2 -2
- data/lib/data_mapper/adapters/data_object_adapter.rb +63 -73
- data/lib/data_mapper/adapters/mysql_adapter.rb +2 -13
- data/lib/data_mapper/adapters/postgresql_adapter.rb +3 -2
- data/lib/data_mapper/adapters/sql/coersion.rb +29 -11
- data/lib/data_mapper/adapters/sql/commands/load_command.rb +13 -9
- data/lib/data_mapper/adapters/sql/mappings/table.rb +7 -3
- data/lib/data_mapper/adapters/sql/quoting.rb +0 -53
- data/lib/data_mapper/adapters/sqlite3_adapter.rb +2 -1
- data/lib/data_mapper/base.rb +25 -9
- data/lib/data_mapper/context.rb +2 -2
- data/lib/data_mapper/database.rb +6 -6
- data/lib/data_mapper/support/active_record_impersonation.rb +1 -1
- data/lib/data_mapper/support/serialization.rb +10 -2
- data/lib/data_mapper/support/silence.rb +10 -0
- data/lib/data_mapper/support/string.rb +21 -1
- data/lib/data_mapper/validations/confirmation_validator.rb +1 -3
- data/lib/data_mapper/validations/format_validator.rb +6 -6
- data/lib/data_mapper/validations/generic_validator.rb +0 -5
- data/lib/data_mapper/validations/length_validator.rb +14 -13
- data/lib/data_mapper/validations/required_field_validator.rb +2 -8
- data/lib/data_mapper/validations/unique_validator.rb +1 -7
- data/plugins/dataobjects/do.rb +94 -2
- data/plugins/dataobjects/do_mysql.rb +17 -17
- data/plugins/dataobjects/do_postgres.rb +34 -15
- data/plugins/dataobjects/do_sqlite3.rb +9 -6
- data/rakefile.rb +3 -2
- data/spec/base_spec.rb +28 -0
- data/spec/dataobjects_spec.rb +2 -1
- data/spec/load_command_spec.rb +1 -1
- data/spec/serialization_spec.rb +2 -2
- data/spec/spec_helper.rb +1 -1
- data/spec/support_spec.rb +7 -0
- data/spec/validations_spec.rb +13 -0
- metadata +14 -6
- data/plugins/dataobjects/swig_mysql/Makefile +0 -146
- data/plugins/dataobjects/swig_mysql/mysql_c.o +0 -0
- data/plugins/dataobjects/swig_postgres/Makefile +0 -146
- data/plugins/dataobjects/swig_sqlite/db +0 -0
data/CHANGELOG
CHANGED
@@ -111,8 +111,16 @@
|
|
111
111
|
* BUG: Column copy for STI moved into Table#initialize to better handle STI with multiple mapped databases
|
112
112
|
* BUG: before_create callbacks moved in the execution flow since they weren't guaranteed to fire before
|
113
113
|
* Threading enhancements: Removed single_threaded_mode, #database block form adjusted for thread-safety
|
114
|
-
* BUG: Fixed String#blank? when a multi-line string contained a blank line (thanks
|
114
|
+
* BUG: Fixed String#blank? when a multi-line string contained a blank line (thanks zapnap!)
|
115
115
|
* Performance enhancements: (thanks wycats!)
|
116
116
|
|
117
117
|
-- 0.2.2
|
118
|
-
* Removed C extension bundles and log files from package
|
118
|
+
* Removed C extension bundles and log files from package
|
119
|
+
|
120
|
+
-- 0.2.3
|
121
|
+
* Added String#t for translation and overrides for default validation messages
|
122
|
+
* Give credit where it's due: zapnap, not pimpmaster, submitted the String#blank? patch. My bad. :-(
|
123
|
+
* MAJOR: Resolve issue with non-unique-hash values and #dirty?; now frozen original values are stored instead
|
124
|
+
* Added Base#update_attributes
|
125
|
+
* MAJOR: Queries are now passed to the database drivers in a parameterized fashion
|
126
|
+
* Updated PostgreSQL driver and adapter to current
|
data/environment.rb
CHANGED
data/lib/data_mapper.rb
CHANGED
@@ -16,6 +16,7 @@ unless defined?(DM_PLUGINS_ROOT)
|
|
16
16
|
end
|
17
17
|
|
18
18
|
# Require the basics...
|
19
|
+
require 'rubygems'
|
19
20
|
require 'yaml'
|
20
21
|
require 'set'
|
21
22
|
require 'fastthread'
|
@@ -23,6 +24,7 @@ require 'data_mapper/support/blank'
|
|
23
24
|
require 'data_mapper/support/enumerable'
|
24
25
|
require 'data_mapper/support/symbol'
|
25
26
|
require 'data_mapper/support/string'
|
27
|
+
require 'data_mapper/support/silence'
|
26
28
|
require 'data_mapper/support/inflector'
|
27
29
|
require 'data_mapper/database'
|
28
30
|
require 'data_mapper/base'
|
@@ -64,7 +64,7 @@ module DataMapper
|
|
64
64
|
@connection_pool.hold { |active_connection| yield(active_connection) }
|
65
65
|
rescue => execution_error
|
66
66
|
# Log error on failure
|
67
|
-
|
67
|
+
logger.error { execution_error }
|
68
68
|
|
69
69
|
# Close all open connections, assuming that if one
|
70
70
|
# had an error, it's likely due to a lost connection,
|
@@ -84,7 +84,7 @@ module DataMapper
|
|
84
84
|
rescue => close_connection_error
|
85
85
|
# An error on closing the connection is almost expected
|
86
86
|
# if the socket is broken.
|
87
|
-
|
87
|
+
logger.warn { close_connection_error }
|
88
88
|
end
|
89
89
|
|
90
90
|
# Reopen fresh connections.
|
@@ -95,31 +95,13 @@ module DataMapper
|
|
95
95
|
raise NotImplementedError.new
|
96
96
|
end
|
97
97
|
|
98
|
-
def execute(*args)
|
99
|
-
connection do |db|
|
100
|
-
sql = escape_sql(*args)
|
101
|
-
log.debug { sql }
|
102
|
-
|
103
|
-
command = db.create_command(sql)
|
104
|
-
|
105
|
-
if block_given?
|
106
|
-
command.execute_reader { |reader| yield(reader) }
|
107
|
-
else
|
108
|
-
command.execute_non_query
|
109
|
-
end
|
110
|
-
end
|
111
|
-
rescue => e
|
112
|
-
handle_error(e)
|
113
|
-
end
|
114
|
-
|
115
98
|
def query(*args)
|
116
99
|
connection do |db|
|
117
|
-
sql = escape_sql(*args)
|
118
|
-
log.debug { sql }
|
119
100
|
|
120
|
-
command = db.create_command(
|
121
|
-
|
122
|
-
|
101
|
+
command = db.create_command(args.shift)
|
102
|
+
logger.debug { command.text }
|
103
|
+
|
104
|
+
command.execute_reader(*args) do |reader|
|
123
105
|
fields = reader.fields.map { |field| Inflector.underscore(field).to_sym }
|
124
106
|
|
125
107
|
results = []
|
@@ -150,19 +132,35 @@ module DataMapper
|
|
150
132
|
end
|
151
133
|
|
152
134
|
def table_exists?(name)
|
153
|
-
|
135
|
+
connection do |db|
|
136
|
+
table = self.table(name)
|
137
|
+
command = db.create_command(table.to_exists_sql)
|
138
|
+
command.execute_reader(table.name, table.schema.name) do |reader|
|
139
|
+
reader.has_rows?
|
140
|
+
end
|
141
|
+
end
|
154
142
|
end
|
155
143
|
|
156
144
|
def truncate(session, name)
|
157
|
-
|
158
|
-
|
159
|
-
|
145
|
+
connection do |db|
|
146
|
+
result = db.create_command("TRUNCATE TABLE #{table(name).to_sql}").execute_non_query
|
147
|
+
session.identity_map.clear!(name)
|
148
|
+
result.to_i > 0
|
149
|
+
end
|
160
150
|
end
|
161
151
|
|
162
152
|
def drop(session, name)
|
163
|
-
|
164
|
-
|
165
|
-
|
153
|
+
table = self.table(name)
|
154
|
+
|
155
|
+
if table.exists?
|
156
|
+
connection do |db|
|
157
|
+
result = db.create_command("DROP TABLE #{table.to_sql}").execute_non_query
|
158
|
+
session.identity_map.clear!(name)
|
159
|
+
true
|
160
|
+
end
|
161
|
+
else
|
162
|
+
false
|
163
|
+
end
|
166
164
|
end
|
167
165
|
|
168
166
|
def create_table(name)
|
@@ -171,7 +169,10 @@ module DataMapper
|
|
171
169
|
if table.exists?
|
172
170
|
false
|
173
171
|
else
|
174
|
-
|
172
|
+
connection do |db|
|
173
|
+
db.create_command(table.to_create_table_sql).execute_non_query
|
174
|
+
true
|
175
|
+
end
|
175
176
|
end
|
176
177
|
end
|
177
178
|
|
@@ -179,15 +180,20 @@ module DataMapper
|
|
179
180
|
table = self.table(instance)
|
180
181
|
|
181
182
|
if instance.is_a?(Class)
|
182
|
-
|
183
|
+
connection do |db|
|
184
|
+
db.create_command("DELETE FROM #{table.to_sql}").execute_non_query
|
185
|
+
end
|
183
186
|
session.identity_map.clear!(instance)
|
184
187
|
else
|
185
188
|
callback(instance, :before_destroy)
|
186
189
|
|
187
|
-
if
|
190
|
+
if connection do |db|
|
191
|
+
command = db.create_command("DELETE FROM #{table.to_sql} WHERE #{table.key.to_sql} = ?")
|
192
|
+
command.execute_non_query(instance.key).to_i > 0
|
193
|
+
end # connection do...end # if continued below:
|
188
194
|
instance.instance_variable_set(:@new_record, true)
|
189
195
|
instance.session = session
|
190
|
-
instance.
|
196
|
+
instance.original_values.clear
|
191
197
|
session.identity_map.delete(instance)
|
192
198
|
callback(instance, :after_destroy)
|
193
199
|
end
|
@@ -210,7 +216,12 @@ module DataMapper
|
|
210
216
|
attributes = instance.dirty_attributes
|
211
217
|
|
212
218
|
unless attributes.empty?
|
213
|
-
|
219
|
+
if table.multi_class?
|
220
|
+
instance.instance_variable_set(
|
221
|
+
table[:type].instance_variable_name,
|
222
|
+
attributes[:type] = instance.class.name
|
223
|
+
)
|
224
|
+
end
|
214
225
|
|
215
226
|
keys = []
|
216
227
|
values = []
|
@@ -220,7 +231,10 @@ module DataMapper
|
|
220
231
|
end
|
221
232
|
|
222
233
|
# Formatting is a bit off here, but it looks nicer in the log this way.
|
223
|
-
insert_id =
|
234
|
+
insert_id = connection do |db|
|
235
|
+
db.create_command("INSERT INTO #{table.to_sql} (#{keys.join(', ')}) VALUES ?")\
|
236
|
+
.execute_non_query(values).last_insert_row
|
237
|
+
end
|
224
238
|
instance.instance_variable_set(:@new_record, false)
|
225
239
|
instance.key = insert_id if table.key.serial? && !attributes.include?(table.key.name)
|
226
240
|
session.identity_map.set(instance)
|
@@ -232,22 +246,28 @@ module DataMapper
|
|
232
246
|
|
233
247
|
table = self.table(instance)
|
234
248
|
attributes = instance.dirty_attributes
|
249
|
+
parameters = []
|
235
250
|
|
236
251
|
unless attributes.empty?
|
237
252
|
sql = "UPDATE " << table.to_sql << " SET "
|
238
253
|
|
239
254
|
sql << attributes.map do |key, value|
|
240
|
-
|
255
|
+
parameters << value
|
256
|
+
"#{table[key].to_sql} = ?"
|
241
257
|
end.join(', ')
|
242
258
|
|
243
|
-
sql << " WHERE #{table.key.to_sql} = "
|
244
|
-
|
245
|
-
|
259
|
+
sql << " WHERE #{table.key.to_sql} = ?"
|
260
|
+
parameters << instance.key
|
261
|
+
|
262
|
+
connection do |db|
|
263
|
+
db.create_command(sql).execute_non_query(*parameters).to_i > 0 \
|
264
|
+
&& callback(instance, :after_update)
|
265
|
+
end
|
246
266
|
end
|
247
267
|
end
|
248
268
|
|
249
269
|
instance.attributes.each_pair do |name, value|
|
250
|
-
instance.
|
270
|
+
instance.original_values[name] = value
|
251
271
|
end
|
252
272
|
|
253
273
|
instance.loaded_associations.each do |association|
|
@@ -259,7 +279,7 @@ module DataMapper
|
|
259
279
|
result
|
260
280
|
end
|
261
281
|
rescue => error
|
262
|
-
|
282
|
+
logger.error(error)
|
263
283
|
raise error
|
264
284
|
end
|
265
285
|
|
@@ -284,36 +304,6 @@ module DataMapper
|
|
284
304
|
instance.class.callbacks.execute(callback_name, instance)
|
285
305
|
end
|
286
306
|
|
287
|
-
# Escape a string of SQL with a set of arguments.
|
288
|
-
# The first argument is assumed to be the SQL to escape,
|
289
|
-
# the remaining arguments (if any) are assumed to be
|
290
|
-
# values to escape and interpolate.
|
291
|
-
#
|
292
|
-
# ==== Examples
|
293
|
-
# escape_sql("SELECT * FROM zoos")
|
294
|
-
# # => "SELECT * FROM zoos"
|
295
|
-
#
|
296
|
-
# escape_sql("SELECT * FROM zoos WHERE name = ?", "Dallas")
|
297
|
-
# # => "SELECT * FROM zoos WHERE name = `Dallas`"
|
298
|
-
#
|
299
|
-
# escape_sql("SELECT * FROM zoos WHERE name = ? AND acreage > ?", "Dallas", 40)
|
300
|
-
# # => "SELECT * FROM zoos WHERE name = `Dallas` AND acreage > 40"
|
301
|
-
#
|
302
|
-
# ==== Warning
|
303
|
-
# This method is meant mostly for adapters that don't support
|
304
|
-
# bind-parameters.
|
305
|
-
def escape_sql(*args)
|
306
|
-
sql = args.shift
|
307
|
-
|
308
|
-
unless args.empty?
|
309
|
-
sql.gsub!(/\?/) do |x|
|
310
|
-
quote_value(args.shift)
|
311
|
-
end
|
312
|
-
end
|
313
|
-
|
314
|
-
sql
|
315
|
-
end
|
316
|
-
|
317
307
|
# This callback copies and sub-classes modules and classes
|
318
308
|
# in the DoAdapter to the inherited class so you don't
|
319
309
|
# have to copy and paste large blocks of code from the
|
@@ -24,27 +24,16 @@ module DataMapper
|
|
24
24
|
builder['dbname', :database]
|
25
25
|
builder['socket', :socket]
|
26
26
|
|
27
|
-
|
27
|
+
logger.debug { connection_string.strip }
|
28
28
|
|
29
29
|
conn = DataObject::Mysql::Connection.new(connection_string.strip)
|
30
|
+
conn.logger = self.logger
|
30
31
|
conn.open
|
31
32
|
cmd = conn.create_command("SET NAMES UTF8")
|
32
33
|
cmd.execute_non_query
|
33
34
|
return conn
|
34
35
|
end
|
35
36
|
|
36
|
-
def quote_time(value)
|
37
|
-
"DATE('#{value.xmlschema}')"
|
38
|
-
end
|
39
|
-
|
40
|
-
def quote_datetime(value)
|
41
|
-
"DATE('#{value}')"
|
42
|
-
end
|
43
|
-
|
44
|
-
def quote_date(value)
|
45
|
-
"DATE('#{value.strftime("%Y-%m-%d")}')"
|
46
|
-
end
|
47
|
-
|
48
37
|
module Mappings
|
49
38
|
|
50
39
|
def to_create_table_sql
|
@@ -28,6 +28,7 @@ module DataMapper
|
|
28
28
|
|
29
29
|
def create_connection
|
30
30
|
conn = DataObject::Postgres::Connection.new("dbname=#{@configuration.database}")
|
31
|
+
conn.logger = self.logger
|
31
32
|
conn.open
|
32
33
|
return conn
|
33
34
|
|
@@ -75,8 +76,8 @@ module DataMapper
|
|
75
76
|
@to_exists_sql || @to_exists_sql = <<-EOS.compress_lines
|
76
77
|
SELECT TABLE_NAME
|
77
78
|
FROM INFORMATION_SCHEMA.TABLES
|
78
|
-
WHERE TABLE_NAME =
|
79
|
-
AND TABLE_CATALOG =
|
79
|
+
WHERE TABLE_NAME = ?
|
80
|
+
AND TABLE_CATALOG = ?
|
80
81
|
EOS
|
81
82
|
end
|
82
83
|
|
@@ -33,12 +33,14 @@ module DataMapper
|
|
33
33
|
|
34
34
|
def type_cast_string(raw_value)
|
35
35
|
return nil if raw_value.blank?
|
36
|
-
|
36
|
+
# type-cast values should be immutable for memory conservation
|
37
|
+
raw_value.freeze
|
37
38
|
end
|
38
39
|
|
39
40
|
def type_cast_text(raw_value)
|
40
41
|
return nil if raw_value.blank?
|
41
|
-
|
42
|
+
# type-cast values should be immutable for memory conservation
|
43
|
+
raw_value.freeze
|
42
44
|
end
|
43
45
|
|
44
46
|
def type_cast_class(raw_value)
|
@@ -48,13 +50,13 @@ module DataMapper
|
|
48
50
|
|
49
51
|
def type_cast_integer(raw_value)
|
50
52
|
return nil if raw_value.blank?
|
51
|
-
raw_value.to_i
|
53
|
+
raw_value.to_i
|
52
54
|
rescue ArgumentError
|
53
55
|
nil
|
54
56
|
end
|
55
57
|
|
56
|
-
def type_cast_decimal(raw_value)
|
57
|
-
return nil if raw_value.blank?
|
58
|
+
def type_cast_decimal(raw_value)
|
59
|
+
return nil if raw_value.blank?
|
58
60
|
raw_value.to_d
|
59
61
|
rescue ArgumentError
|
60
62
|
nil
|
@@ -62,14 +64,18 @@ module DataMapper
|
|
62
64
|
|
63
65
|
def type_cast_float(raw_value)
|
64
66
|
return nil if raw_value.blank?
|
65
|
-
raw_value
|
67
|
+
case raw_value
|
68
|
+
when Float then raw_value
|
69
|
+
when Numeric, String then raw_value.to_f
|
70
|
+
else CoersionError.new("Can't type-cast #{raw_value.inspect} to a float")
|
71
|
+
end
|
66
72
|
end
|
67
73
|
|
68
74
|
def type_cast_datetime(raw_value)
|
69
75
|
return nil if raw_value.blank?
|
70
76
|
|
71
77
|
case raw_value
|
72
|
-
when DateTime then raw_value
|
78
|
+
when DateTime then raw_value.freeze
|
73
79
|
when Date then DateTime.new(raw_value)
|
74
80
|
when String then DateTime::parse(raw_value)
|
75
81
|
else raise CoersionError.new("Can't type-cast #{raw_value.inspect} to a datetime")
|
@@ -80,7 +86,7 @@ module DataMapper
|
|
80
86
|
return nil if raw_value.blank?
|
81
87
|
|
82
88
|
case raw_value
|
83
|
-
when Date then raw_value
|
89
|
+
when Date then raw_value.freeze
|
84
90
|
when DateTime, Time then Date::civil(raw_value.year, raw_value.month, raw_value.day)
|
85
91
|
when String then Date::parse(raw_value)
|
86
92
|
else raise CoersionError.new("Can't type-cast #{raw_value.inspect} to a date")
|
@@ -90,10 +96,22 @@ module DataMapper
|
|
90
96
|
def type_cast_value(type, raw_value)
|
91
97
|
return nil if raw_value.blank?
|
92
98
|
|
93
|
-
|
94
|
-
|
99
|
+
case type
|
100
|
+
when :string then type_cast_string(raw_value)
|
101
|
+
when :text then type_cast_text(raw_value)
|
102
|
+
when :boolean then type_cast_boolean(raw_value)
|
103
|
+
when :class then type_cast_class(raw_value)
|
104
|
+
when :integer then type_cast_integer(raw_value)
|
105
|
+
when :decimal then type_cast_decimal(raw_value)
|
106
|
+
when :float then type_cast_float(raw_value)
|
107
|
+
when :datetime then type_cast_datetime(raw_value)
|
108
|
+
when :date then type_cast_date(raw_value)
|
95
109
|
else
|
96
|
-
|
110
|
+
if respond_to?("type_cast_#{type}")
|
111
|
+
send("type_cast_#{type}", raw_value)
|
112
|
+
else
|
113
|
+
raise "Don't know how to type-cast #{{ type => raw_value }.inspect }"
|
114
|
+
end
|
97
115
|
end
|
98
116
|
end
|
99
117
|
|
@@ -48,17 +48,17 @@ module DataMapper
|
|
48
48
|
|
49
49
|
@klass.callbacks.execute(:before_materialize, instance)
|
50
50
|
|
51
|
-
|
51
|
+
original_values = instance.original_values
|
52
52
|
|
53
53
|
@columns.each_pair do |index, column|
|
54
54
|
# This may be a little confusing, but we're
|
55
|
-
# setting both the
|
55
|
+
# setting both the original_value, and the
|
56
56
|
# instance-variable through method chaining to avoid
|
57
57
|
# lots of extra short-lived local variables.
|
58
|
-
|
58
|
+
original_values[column.name] = instance.instance_variable_set(
|
59
59
|
column.instance_variable_name,
|
60
60
|
column.type_cast_value(values[index])
|
61
|
-
)
|
61
|
+
)
|
62
62
|
end
|
63
63
|
|
64
64
|
instance.instance_variable_set(:@loaded_set, @set)
|
@@ -200,11 +200,15 @@ module DataMapper
|
|
200
200
|
results = []
|
201
201
|
|
202
202
|
# Execute the statement and load the objects.
|
203
|
-
@adapter.
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
203
|
+
@adapter.connection do |db|
|
204
|
+
sql, *parameters = to_parameterized_sql
|
205
|
+
command = db.create_command(sql)
|
206
|
+
command.execute_reader(*parameters) do |reader|
|
207
|
+
if @options.has_key?(:intercept_load)
|
208
|
+
load(reader, &@options[:intercept_load])
|
209
|
+
else
|
210
|
+
load(reader)
|
211
|
+
end
|
208
212
|
end
|
209
213
|
end
|
210
214
|
|