Class: DataMapper::Adapters::DataObjectAdapter

Inherits:
AbstractAdapter show all
Includes:
Coersion, Sql, Quoting
Defined in:
lib/data_mapper/adapters/data_object_adapter.rb

Overview

You must inherit from the DoAdapter, and implement the required methods to adapt a database library for use with the DataMapper.

NOTE: By inheriting from DoAdapter, you get a copy of all the standard sub-modules (Quoting, Coersion and Queries) in your own Adapter. You can extend and overwrite these copies without affecting the originals.

Direct Known Subclasses

MysqlAdapter, PostgresqlAdapter, Sqlite3Adapter

Constant Summary collapse

FIND_OPTIONS =
[
  :select, :offset, :limit, :class, :include, :shallow_include, :reload, :conditions, :order, :intercept_load
]
TABLE_QUOTING_CHARACTER =
'`'.freeze
COLUMN_QUOTING_CHARACTER =
'`'.freeze
TYPES =
{
  :integer => 'int'.freeze,
  :string => 'varchar'.freeze,
  :text => 'text'.freeze,
  :class => 'varchar'.freeze,
  :decimal => 'decimal'.freeze,
  :float => 'float'.freeze,
  :datetime => 'datetime'.freeze,
  :date => 'date'.freeze,
  :boolean => 'boolean'.freeze,
  :object => 'text'.freeze
}

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from AbstractAdapter

#index_path, #logger, #name

Constructor Details

#initialize(configuration) ⇒ DataObjectAdapter

Returns a new instance of DataObjectAdapter.



50
51
52
53
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 50

def initialize(configuration)
  super
  @connection_pool = Support::ConnectionPool.new { create_connection }
end

Class Method Details

.inherited(base) ⇒ Object

This callback copies and sub-classes modules and classes in the DoAdapter to the inherited class so you don’t have to copy and paste large blocks of code from the DoAdapter.

Basically, when inheriting from the DoAdapter, you aren’t just inheriting a single class, you’re inheriting a whole graph of Types. For convenience.



334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 334

def self.inherited(base)
  
  commands = base.const_set('Commands', Module.new)

  Sql::Commands.constants.each do |name|
    commands.const_set(name, Class.new(Sql::Commands.const_get(name)))
  end
  
  mappings = base.const_set('Mappings', Module.new)
  
  Sql::Mappings.constants.each do |name|
    mappings.const_set(name, Class.new(Sql::Mappings.const_get(name)))
  end
  
  base.const_set('TYPES', TYPES.dup)
  base.const_set('FIND_OPTIONS', FIND_OPTIONS.dup)
  
  super
end

Instance Method Details

#activate!Object



59
60
61
62
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 59

def activate!
  @activated = true
  schema.activate!
end

#activated?Boolean

Returns:

  • (Boolean)


55
56
57
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 55

def activated?
  @activated
end

#batch_insertable?Boolean

Returns:

  • (Boolean)


68
69
70
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 68

def batch_insertable?
  true
end

#callback(instance, callback_name) ⇒ Object



322
323
324
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 322

def callback(instance, callback_name)
  instance.class.callbacks.execute(callback_name, instance)
end

#column_exists_for_table?(table_name, column_name) ⇒ Boolean

Returns:

  • (Boolean)


160
161
162
163
164
165
166
167
168
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 160

def column_exists_for_table?(table_name, column_name)
  connection do |db|
    table = self.table(table_name)
    command = db.create_command(table.to_column_exists_sql)
    command.execute_reader(table.name, column_name, table.schema.name) do |reader|
      reader.any? { reader.item(1) == column_name.to_s }
    end
  end
end

#connectionObject

Yields an available connection. Flushes the connection-pool and reconnects if the connection returns an error.



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 74

def connection
  begin
    # Yield the appropriate connection
    @connection_pool.hold { |active_connection| yield(active_connection) }
  rescue => execution_error
    # Log error on failure
    logger.error { execution_error }
    
    # Close all open connections, assuming that if one
    # had an error, it's likely due to a lost connection,
    # in which case all connections are likely broken.
    flush_connections!
    
    raise execution_error
  end
end

#create(database_context, instance) ⇒ Object



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 259

def create(database_context, instance)
  callback(instance, :before_create)

  table = self.table(instance)
  attributes = instance.dirty_attributes

  if table.multi_class?
    instance.instance_variable_set(
      table[:type].instance_variable_name,
      attributes[:type] = instance.class.name
    )
  end

  keys = []
  values = []
  attributes.each_pair do |key, value|
    keys << table[key].to_sql
    values << value
  end

  sql = if keys.size > 0
    "INSERT INTO #{table.to_sql} (#{keys.join(', ')}) VALUES ?"
  else
    "INSERT INTO #{table.to_sql}"
  end
  
  insert_id = connection do |db|
    db.create_command(sql).execute_non_query(values).last_insert_row
  end
  instance.instance_variable_set(:@new_record, false)
  instance.key = insert_id if table.key.serial? && !attributes.include?(table.key.name)
  database_context.identity_map.set(instance)
  callback(instance, :after_create)
end

#create_connectionObject

Raises:

  • (NotImplementedError)


64
65
66
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 64

def create_connection
  raise NotImplementedError.new
end

#delete(database_context, instance) ⇒ Object



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 170

def delete(database_context, instance)
  table = self.table(instance)
  
  if instance.is_a?(Class)
    table.delete_all!
  else
    callback(instance, :before_destroy)
    
    if table.paranoid?
      instance.instance_variable_set(table.paranoid_column.instance_variable_name, Time::now)
      instance.save
    else
      if connection do |db|
          command = db.create_command("DELETE FROM #{table.to_sql} WHERE #{table.key.to_sql} = ?")
          command.execute_non_query(instance.key).to_i > 0
        end # connection do...end # if continued below:
        instance.instance_variable_set(:@new_record, true)
        instance.database_context = database_context
        instance.original_values.clear
        database_context.identity_map.delete(instance)
        callback(instance, :after_destroy)
      end
    end        
  end
end

#execute(*args) ⇒ Object



141
142
143
144
145
146
147
148
149
150
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 141

def execute(*args)
  db = create_connection
  command = db.create_command(args.shift)
  return command.execute_non_query(*args)
rescue => e
  logger.error { e }
  raise e
ensure
  db.close
end

#flush_connections!Object

Close any open connections.



92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 92

def flush_connections!
  begin
    @connection_pool.available_connections.each do |active_connection|
      active_connection.close
    end
  rescue => close_connection_error
    # An error on closing the connection is almost expected
    # if the socket is broken.
    logger.warn { close_connection_error }
  end
  
  # Reopen fresh connections.
  @connection_pool.available_connections.clear
end

#get(database_context, klass, keys) ⇒ Object



298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 298

def get(database_context, klass, keys)
  table = self.table(klass)
  sql = "SELECT #{table.columns.map { |column| column.to_sql }.join(', ')} FROM #{table.to_sql} WHERE #{table.keys.map { |key| "#{key.to_sql} = ?" }.join(' AND ')}"
  
  connection do |db|
    db.create_command(sql).execute_reader(*keys) do |reader|
      values = {}
      table.columns.each_with_index do |column, i|
        values[column.name] = reader.item(i)
      end
      materialize(database_context, klass, values, false, [])
    end
  end
end

#handle_error(error) ⇒ Object



152
153
154
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 152

def handle_error(error)
  raise error
end

#load(database_context, klass, options) ⇒ Object



294
295
296
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 294

def load(database_context, klass, options)
  self.class::Commands::LoadCommand.new(self, database_context, klass, options).call
end

#query(*args) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 111

def query(*args)
  db = create_connection
  
  command = db.create_command(args.shift)
  
  reader = command.execute_reader(*args)
  fields = reader.fields.map { |field| Inflector.underscore(field).to_sym }
  results = []

  if fields.size > 1
    struct = Struct.new(*fields)
    
    reader.each do
      results << struct.new(*reader.current_row)
    end
  else
    reader.each do
      results << reader.item(0)
    end
  end

  return results
rescue => e
  logger.error { e }
  raise e
ensure
  reader.close if reader
  db.close
end

#save(database_context, instance, validate = true) ⇒ Object



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 196

def save(database_context, instance, validate = true)
  case instance
  when Class then table(instance).create!
  when Mappings::Table then instance.create!
  when DataMapper::Persistence then          
    event = instance.new_record? ? :create : :update
    
    return false if validate && !instance.validate_recursively(event, Set.new)
    
    callback(instance, :before_save)
    
    return false unless instance.new_record? || instance.dirty?
    
    result = send(event, database_context, instance)
    
    instance.database_context = database_context
    instance.attributes.each_pair do |name, value|
      instance.original_values[name] = value
    end
    
    instance.loaded_associations.each do |association|
      association.save_without_validation(database_context) if association.dirty?
    end
    
    callback(instance, :after_save)
    result
  end
rescue => error
  logger.error(error)
  raise error
end

#save_without_validation(database_context, instance) ⇒ Object



228
229
230
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 228

def save_without_validation(database_context, instance)
  save(database_context, instance, false)
end

#schemaObject



156
157
158
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 156

def schema
  @schema || ( @schema = self.class::Mappings::Schema.new(self, @configuration.database) )
end

#table(instance) ⇒ Object



313
314
315
316
317
318
319
320
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 313

def table(instance)
  case instance
  when DataMapper::Adapters::Sql::Mappings::Table then instance
  when DataMapper::Persistence then schema[instance.class]
  when Class, String then schema[instance]
  else raise "Don't know how to map #{instance.inspect} to a table."
  end
end

#transaction(&block) ⇒ Object

Raises:

  • (NotImplementedError)


107
108
109
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 107

def transaction(&block)
  raise NotImplementedError.new
end

#update(database_context, instance) ⇒ Object



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/data_mapper/adapters/data_object_adapter.rb', line 232

def update(database_context, instance)
  callback(instance, :before_update)
  
  table = self.table(instance)
  attributes = instance.dirty_attributes
  parameters = []
  
  unless attributes.empty?
    sql = "UPDATE " << table.to_sql << " SET "
  
    sql << attributes.map do |key, value|
      parameters << value
      "#{table[key].to_sql} = ?"
    end.join(', ')
  
    sql << " WHERE #{table.key.to_sql} = ?"
    parameters << instance.key
    
    connection do |db|
      db.create_command(sql).execute_non_query(*parameters).to_i > 0 \
      && callback(instance, :after_update)
    end
  else
    true
  end
end