datamapper 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|