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.
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