datamapper 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/CHANGELOG +10 -2
  2. data/environment.rb +0 -3
  3. data/lib/data_mapper.rb +2 -0
  4. data/lib/data_mapper/adapters/abstract_adapter.rb +2 -2
  5. data/lib/data_mapper/adapters/data_object_adapter.rb +63 -73
  6. data/lib/data_mapper/adapters/mysql_adapter.rb +2 -13
  7. data/lib/data_mapper/adapters/postgresql_adapter.rb +3 -2
  8. data/lib/data_mapper/adapters/sql/coersion.rb +29 -11
  9. data/lib/data_mapper/adapters/sql/commands/load_command.rb +13 -9
  10. data/lib/data_mapper/adapters/sql/mappings/table.rb +7 -3
  11. data/lib/data_mapper/adapters/sql/quoting.rb +0 -53
  12. data/lib/data_mapper/adapters/sqlite3_adapter.rb +2 -1
  13. data/lib/data_mapper/base.rb +25 -9
  14. data/lib/data_mapper/context.rb +2 -2
  15. data/lib/data_mapper/database.rb +6 -6
  16. data/lib/data_mapper/support/active_record_impersonation.rb +1 -1
  17. data/lib/data_mapper/support/serialization.rb +10 -2
  18. data/lib/data_mapper/support/silence.rb +10 -0
  19. data/lib/data_mapper/support/string.rb +21 -1
  20. data/lib/data_mapper/validations/confirmation_validator.rb +1 -3
  21. data/lib/data_mapper/validations/format_validator.rb +6 -6
  22. data/lib/data_mapper/validations/generic_validator.rb +0 -5
  23. data/lib/data_mapper/validations/length_validator.rb +14 -13
  24. data/lib/data_mapper/validations/required_field_validator.rb +2 -8
  25. data/lib/data_mapper/validations/unique_validator.rb +1 -7
  26. data/plugins/dataobjects/do.rb +94 -2
  27. data/plugins/dataobjects/do_mysql.rb +17 -17
  28. data/plugins/dataobjects/do_postgres.rb +34 -15
  29. data/plugins/dataobjects/do_sqlite3.rb +9 -6
  30. data/rakefile.rb +3 -2
  31. data/spec/base_spec.rb +28 -0
  32. data/spec/dataobjects_spec.rb +2 -1
  33. data/spec/load_command_spec.rb +1 -1
  34. data/spec/serialization_spec.rb +2 -2
  35. data/spec/spec_helper.rb +1 -1
  36. data/spec/support_spec.rb +7 -0
  37. data/spec/validations_spec.rb +13 -0
  38. metadata +14 -6
  39. data/plugins/dataobjects/swig_mysql/Makefile +0 -146
  40. data/plugins/dataobjects/swig_mysql/mysql_c.o +0 -0
  41. data/plugins/dataobjects/swig_postgres/Makefile +0 -146
  42. 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 pimpmaster!)
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
@@ -1,6 +1,3 @@
1
- MERB_ROOT = Dir::pwd
2
- MERB_ENV = 'development'
3
-
4
1
  # Require the DataMapper, and a Mock Adapter.
5
2
  require File.dirname(__FILE__) + '/lib/data_mapper'
6
3
  require File.dirname(__FILE__) + '/spec/mock_adapter'
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'
@@ -29,8 +29,8 @@ module DataMapper
29
29
  raise NotImplementedError.new
30
30
  end
31
31
 
32
- def log
33
- @configuration.log
32
+ def logger
33
+ @logger || @logger = @configuration.logger
34
34
  end
35
35
 
36
36
  end # class AbstractAdapter
@@ -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
- @configuration.log.error(execution_error)
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
- @configuration.log.warn(close_connection_error)
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(sql)
121
-
122
- command.execute_reader do |reader|
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
- execute(table(name).to_exists_sql) { |reader| reader.has_rows? }
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
- result = execute("TRUNCATE TABLE #{table(name).to_sql}")
158
- session.identity_map.clear!(name)
159
- result.to_i > 0
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
- result = execute("DROP TABLE #{table(name).to_sql}")
164
- session.identity_map.clear!(name)
165
- true
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
- execute(table.to_create_table_sql); true
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
- execute("DELETE FROM #{table.to_sql}")
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 execute("DELETE FROM #{table.to_sql} WHERE #{table.key.to_sql} = #{quote_value(instance.key)}").to_i > 0
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.original_hashes.clear
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
- attributes[:type] = instance.class.name if table.multi_class?
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 = execute("INSERT INTO #{table.to_sql} (#{keys.join(', ')}) VALUES (#{values.map { |v| quote_value(v) }.join(', ')})").last_insert_row
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
- "#{table[key].to_sql} = #{quote_value(value)}"
255
+ parameters << value
256
+ "#{table[key].to_sql} = ?"
241
257
  end.join(', ')
242
258
 
243
- sql << " WHERE #{table.key.to_sql} = " << quote_value(instance.key)
244
-
245
- execute(sql).to_i > 0 && callback(instance, :after_update)
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.original_hashes[name] = value.hash
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
- log.error(error)
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
- log.debug { connection_string.strip }
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 = #{@adapter.quote_value(name)}
79
- AND TABLE_CATALOG = #{@adapter.quote_value(@adapter.schema.name)}
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
- raw_value
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
- raw_value
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 # Integer(raw_value) would be "safer", but not as fast.
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.to_f
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
- if respond_to?("type_cast_#{type}")
94
- send("type_cast_#{type}", raw_value)
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
- raise "Don't know how to type-cast #{{ type => raw_value }.inspect }"
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
- original_hashes = instance.original_hashes
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 original-hash value, and 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
- original_hashes[column.name] = instance.instance_variable_set(
58
+ original_values[column.name] = instance.instance_variable_set(
59
59
  column.instance_variable_name,
60
60
  column.type_cast_value(values[index])
61
- ).hash
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.execute(*to_parameterized_sql) do |reader, num_rows|
204
- if @options.has_key?(:intercept_load)
205
- load(reader, &@options[:intercept_load])
206
- else
207
- load(reader)
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