datamapper 0.2.5 → 0.3.0
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 +5 -1
- data/FAQ +96 -0
- data/QUICKLINKS +12 -0
- data/README +57 -155
- data/environment.rb +61 -43
- data/example.rb +30 -12
- data/lib/data_mapper.rb +6 -1
- data/lib/data_mapper/adapters/abstract_adapter.rb +0 -57
- data/lib/data_mapper/adapters/data_object_adapter.rb +203 -97
- data/lib/data_mapper/adapters/mysql_adapter.rb +4 -0
- data/lib/data_mapper/adapters/postgresql_adapter.rb +7 -1
- data/lib/data_mapper/adapters/sql/coersion.rb +3 -2
- data/lib/data_mapper/adapters/sql/commands/load_command.rb +29 -10
- data/lib/data_mapper/adapters/sql/mappings/associations_set.rb +4 -0
- data/lib/data_mapper/adapters/sql/mappings/column.rb +13 -9
- data/lib/data_mapper/adapters/sql/mappings/conditions.rb +172 -0
- data/lib/data_mapper/adapters/sql/mappings/table.rb +43 -17
- data/lib/data_mapper/adapters/sqlite3_adapter.rb +9 -2
- data/lib/data_mapper/associations.rb +75 -3
- data/lib/data_mapper/associations/belongs_to_association.rb +70 -36
- data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +195 -86
- data/lib/data_mapper/associations/has_many_association.rb +168 -61
- data/lib/data_mapper/associations/has_n_association.rb +23 -3
- data/lib/data_mapper/attributes.rb +73 -0
- data/lib/data_mapper/auto_migrations.rb +2 -6
- data/lib/data_mapper/base.rb +5 -9
- data/lib/data_mapper/database.rb +4 -3
- data/lib/data_mapper/embedded_value.rb +66 -30
- data/lib/data_mapper/identity_map.rb +1 -3
- data/lib/data_mapper/is/tree.rb +121 -0
- data/lib/data_mapper/migration.rb +155 -0
- data/lib/data_mapper/persistence.rb +532 -218
- data/lib/data_mapper/property.rb +306 -0
- data/lib/data_mapper/query.rb +164 -0
- data/lib/data_mapper/support/blank.rb +2 -2
- data/lib/data_mapper/support/connection_pool.rb +5 -6
- data/lib/data_mapper/support/enumerable.rb +3 -3
- data/lib/data_mapper/support/errors.rb +10 -1
- data/lib/data_mapper/support/inflector.rb +174 -238
- data/lib/data_mapper/support/object.rb +54 -0
- data/lib/data_mapper/support/serialization.rb +19 -1
- data/lib/data_mapper/support/string.rb +7 -16
- data/lib/data_mapper/support/symbol.rb +3 -15
- data/lib/data_mapper/support/typed_set.rb +68 -0
- data/lib/data_mapper/types/base.rb +44 -0
- data/lib/data_mapper/types/string.rb +34 -0
- data/lib/data_mapper/validations/number_validator.rb +40 -0
- data/lib/data_mapper/validations/string_validator.rb +20 -0
- data/lib/data_mapper/validations/validator.rb +13 -0
- data/performance.rb +26 -1
- data/profile_data_mapper.rb +1 -1
- data/rakefile.rb +42 -2
- data/spec/acts_as_tree_spec.rb +11 -3
- data/spec/adapters/data_object_adapter_spec.rb +31 -0
- data/spec/associations/belongs_to_association_spec.rb +98 -0
- data/spec/associations/has_and_belongs_to_many_association_spec.rb +377 -0
- data/spec/associations/has_many_association_spec.rb +337 -0
- data/spec/attributes_spec.rb +23 -1
- data/spec/auto_migrations_spec.rb +86 -29
- data/spec/callbacks_spec.rb +107 -0
- data/spec/column_spec.rb +5 -2
- data/spec/count_command_spec.rb +33 -1
- data/spec/database_spec.rb +18 -0
- data/spec/dependency_spec.rb +4 -2
- data/spec/embedded_value_spec.rb +8 -8
- data/spec/fixtures/people.yaml +1 -1
- data/spec/fixtures/projects.yaml +10 -1
- data/spec/fixtures/tasks.yaml +6 -0
- data/spec/fixtures/tasks_tasks.yaml +2 -0
- data/spec/fixtures/tomatoes.yaml +1 -0
- data/spec/is_a_tree_spec.rb +149 -0
- data/spec/load_command_spec.rb +71 -9
- data/spec/magic_columns_spec.rb +17 -2
- data/spec/migration_spec.rb +267 -0
- data/spec/models/animal.rb +1 -1
- data/spec/models/candidate.rb +8 -0
- data/spec/models/career.rb +1 -1
- data/spec/models/chain.rb +8 -0
- data/spec/models/comment.rb +1 -1
- data/spec/models/exhibit.rb +1 -1
- data/spec/models/fence.rb +7 -0
- data/spec/models/fruit.rb +2 -2
- data/spec/models/job.rb +8 -0
- data/spec/models/person.rb +2 -3
- data/spec/models/post.rb +1 -1
- data/spec/models/project.rb +21 -1
- data/spec/models/section.rb +1 -1
- data/spec/models/serializer.rb +1 -1
- data/spec/models/task.rb +9 -0
- data/spec/models/tomato.rb +27 -0
- data/spec/models/user.rb +8 -2
- data/spec/models/zoo.rb +2 -7
- data/spec/paranoia_spec.rb +1 -1
- data/spec/{base_spec.rb → persistence_spec.rb} +207 -18
- data/spec/postgres_spec.rb +48 -6
- data/spec/property_spec.rb +90 -9
- data/spec/query_spec.rb +71 -5
- data/spec/save_command_spec.rb +11 -0
- data/spec/spec_helper.rb +14 -11
- data/spec/support/blank_spec.rb +8 -0
- data/spec/support/inflector_spec.rb +41 -0
- data/spec/support/object_spec.rb +9 -0
- data/spec/{serialization_spec.rb → support/serialization_spec.rb} +1 -1
- data/spec/support/silence_spec.rb +15 -0
- data/spec/{support_spec.rb → support/string_spec.rb} +3 -3
- data/spec/support/struct_spec.rb +12 -0
- data/spec/support/typed_set_spec.rb +66 -0
- data/spec/table_spec.rb +3 -3
- data/spec/types/string.rb +81 -0
- data/spec/validates_uniqueness_of_spec.rb +17 -0
- data/spec/validations/number_validator.rb +59 -0
- data/spec/validations/string_validator.rb +14 -0
- metadata +59 -17
- data/do_performance.rb +0 -153
- data/lib/data_mapper/support/active_record_impersonation.rb +0 -103
- data/lib/data_mapper/support/weak_hash.rb +0 -46
- data/spec/active_record_impersonation_spec.rb +0 -129
- data/spec/associations_spec.rb +0 -232
- data/spec/conditions_spec.rb +0 -49
- data/spec/has_many_association_spec.rb +0 -173
- data/spec/models/animals_exhibit.rb +0 -8
data/example.rb
CHANGED
@@ -1,27 +1,45 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
ENV['LOG_NAME']
|
3
|
+
ENV['LOG_NAME'] ||= 'example'
|
4
4
|
require 'environment'
|
5
5
|
|
6
6
|
# Define a fixtures helper method to load up our test data.
|
7
|
-
def fixtures(name
|
7
|
+
def fixtures(name)
|
8
8
|
entry = YAML::load_file(File.dirname(__FILE__) + "/spec/fixtures/#{name}.yaml")
|
9
|
-
klass =
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
9
|
+
klass = begin
|
10
|
+
Kernel::const_get(Inflector.classify(Inflector.singularize(name)))
|
11
|
+
rescue
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
|
15
|
+
unless klass.nil?
|
16
|
+
database.logger.debug { "AUTOMIGRATE: #{klass}" }
|
17
|
+
klass.auto_migrate!
|
18
|
+
|
19
|
+
(entry.kind_of?(Array) ? entry : [entry]).each do |hash|
|
20
|
+
if hash['type']
|
21
|
+
Object::const_get(hash['type'])::create(hash)
|
22
|
+
else
|
23
|
+
klass::create(hash)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
else
|
27
|
+
table = database.table(name.to_s)
|
28
|
+
table.create! true
|
29
|
+
table.activate_associations!
|
30
|
+
|
31
|
+
#pp database.schema
|
32
|
+
|
33
|
+
(entry.kind_of?(Array) ? entry : [entry]).each do |hash|
|
34
|
+
table.insert(hash)
|
18
35
|
end
|
19
36
|
end
|
20
37
|
end
|
21
38
|
|
39
|
+
|
22
40
|
# Pre-fill the database so non-destructive tests don't need to reload fixtures.
|
23
41
|
Dir[File.dirname(__FILE__) + "/spec/fixtures/*.yaml"].each do |path|
|
24
|
-
fixtures(File::basename(path).sub(/\.yaml$/, '')
|
42
|
+
fixtures(File::basename(path).sub(/\.yaml$/, ''))
|
25
43
|
end
|
26
44
|
|
27
45
|
require 'irb'
|
data/lib/data_mapper.rb
CHANGED
@@ -16,11 +16,14 @@ unless defined?(DM_PLUGINS_ROOT)
|
|
16
16
|
end
|
17
17
|
|
18
18
|
# Require the basics...
|
19
|
+
require 'date'
|
20
|
+
require 'time'
|
19
21
|
require 'rubygems'
|
20
22
|
require 'yaml'
|
21
23
|
require 'set'
|
22
24
|
require 'fastthread'
|
23
25
|
require 'validatable'
|
26
|
+
require 'data_mapper/support/object'
|
24
27
|
require 'data_mapper/support/blank'
|
25
28
|
require 'data_mapper/support/enumerable'
|
26
29
|
require 'data_mapper/support/symbol'
|
@@ -28,9 +31,11 @@ require 'data_mapper/support/string'
|
|
28
31
|
require 'data_mapper/support/silence'
|
29
32
|
require 'data_mapper/support/inflector'
|
30
33
|
require 'data_mapper/support/errors'
|
34
|
+
require 'data_mapper/support/typed_set'
|
31
35
|
require 'data_mapper/database'
|
32
36
|
require 'data_mapper/persistence'
|
33
37
|
require 'data_mapper/base'
|
38
|
+
require 'data_mapper/types/string'
|
34
39
|
|
35
40
|
|
36
41
|
begin
|
@@ -80,4 +85,4 @@ begin
|
|
80
85
|
end
|
81
86
|
rescue Exception
|
82
87
|
warn "Could not connect to database specified by database.yml."
|
83
|
-
end
|
88
|
+
end
|
@@ -37,63 +37,6 @@ module DataMapper
|
|
37
37
|
@logger || @logger = @configuration.logger
|
38
38
|
end
|
39
39
|
|
40
|
-
protected
|
41
|
-
|
42
|
-
def materialize(database_context, klass, values, reload, loaded_set)
|
43
|
-
|
44
|
-
table = self.table(klass)
|
45
|
-
|
46
|
-
instance_id = table.key.type_cast_value(values[table.key.name])
|
47
|
-
|
48
|
-
instance_type = if table.multi_class? && table.type_column
|
49
|
-
values[table.type_column.name].blank? ? klass :
|
50
|
-
table.type_column.type_cast_value(values[table.type_column.name])
|
51
|
-
else
|
52
|
-
klass
|
53
|
-
end
|
54
|
-
|
55
|
-
instance = create_instance(database_context, instance_type, instance_id, reload)
|
56
|
-
|
57
|
-
instance_type.callbacks.execute(:before_materialize, instance)
|
58
|
-
|
59
|
-
type_casted_values = {}
|
60
|
-
|
61
|
-
values.each_pair do |k,v|
|
62
|
-
column = table[k]
|
63
|
-
type_cast_value = column.type_cast_value(v)
|
64
|
-
type_casted_values[k] = type_cast_value
|
65
|
-
instance.instance_variable_set(column.instance_variable_name, type_cast_value)
|
66
|
-
end
|
67
|
-
|
68
|
-
instance.original_values = type_casted_values
|
69
|
-
instance.loaded_set = loaded_set
|
70
|
-
|
71
|
-
instance_type.callbacks.execute(:after_materialize, instance)
|
72
|
-
|
73
|
-
return instance
|
74
|
-
|
75
|
-
#rescue => e
|
76
|
-
# raise MaterializationError.new("Failed to materialize row: #{values.inspect}\n#{e.to_yaml}")
|
77
|
-
end
|
78
|
-
|
79
|
-
def create_instance(database_context, instance_type, instance_id, reload)
|
80
|
-
instance = database_context.identity_map.get(instance_type, instance_id)
|
81
|
-
|
82
|
-
if instance.nil? || reload
|
83
|
-
instance = instance_type.new() if instance.nil?
|
84
|
-
instance.instance_variable_set(:@__key, instance_id)
|
85
|
-
instance.instance_variable_set(:@new_record, false)
|
86
|
-
database_context.identity_map.set(instance)
|
87
|
-
elsif instance.new_record?
|
88
|
-
instance.instance_variable_set(:@__key, instance_id)
|
89
|
-
instance.instance_variable_set(:@new_record, false)
|
90
|
-
end
|
91
|
-
|
92
|
-
instance.database_context = database_context
|
93
|
-
|
94
|
-
return instance
|
95
|
-
end
|
96
|
-
|
97
40
|
end # class AbstractAdapter
|
98
41
|
|
99
42
|
end # module Adapters
|
@@ -4,12 +4,13 @@ require 'data_mapper/adapters/sql/coersion'
|
|
4
4
|
require 'data_mapper/adapters/sql/quoting'
|
5
5
|
require 'data_mapper/adapters/sql/mappings/schema'
|
6
6
|
require 'data_mapper/support/connection_pool'
|
7
|
+
require 'data_mapper/query'
|
7
8
|
|
8
9
|
module DataMapper
|
9
|
-
|
10
|
+
|
10
11
|
# An Adapter is really a Factory for three types of object,
|
11
12
|
# so they can be selectively sub-classed where needed.
|
12
|
-
#
|
13
|
+
#
|
13
14
|
# The first type is a Query. The Query is an object describing
|
14
15
|
# the database-specific operations we wish to perform, in an
|
15
16
|
# abstract manner. For example: While most if not all databases
|
@@ -29,7 +30,7 @@ module DataMapper
|
|
29
30
|
# If the library being adapted does not provide such functionality,
|
30
31
|
# DataMapper::Support::ConnectionPool can be used.
|
31
32
|
module Adapters
|
32
|
-
|
33
|
+
|
33
34
|
# You must inherit from the DoAdapter, and implement the
|
34
35
|
# required methods to adapt a database library for use with the DataMapper.
|
35
36
|
#
|
@@ -37,38 +38,42 @@ module DataMapper
|
|
37
38
|
# standard sub-modules (Quoting, Coersion and Queries) in your own Adapter.
|
38
39
|
# You can extend and overwrite these copies without affecting the originals.
|
39
40
|
class DataObjectAdapter < AbstractAdapter
|
40
|
-
|
41
|
+
|
41
42
|
$LOAD_PATH << (DM_PLUGINS_ROOT + '/dataobjects')
|
42
|
-
|
43
|
+
|
43
44
|
FIND_OPTIONS = [
|
44
45
|
:select, :offset, :limit, :class, :include, :shallow_include, :reload, :conditions, :order, :intercept_load
|
45
46
|
]
|
46
|
-
|
47
|
+
|
47
48
|
TABLE_QUOTING_CHARACTER = '`'.freeze
|
48
49
|
COLUMN_QUOTING_CHARACTER = '`'.freeze
|
49
|
-
|
50
|
+
|
51
|
+
SYNTAX = {
|
52
|
+
:now => 'NOW()'.freeze
|
53
|
+
}
|
54
|
+
|
50
55
|
def initialize(configuration)
|
51
56
|
super
|
52
57
|
@connection_pool = Support::ConnectionPool.new { create_connection }
|
53
58
|
end
|
54
|
-
|
59
|
+
|
55
60
|
def activated?
|
56
61
|
@activated
|
57
62
|
end
|
58
|
-
|
63
|
+
|
59
64
|
def activate!
|
60
65
|
@activated = true
|
61
66
|
schema.activate!
|
62
67
|
end
|
63
|
-
|
68
|
+
|
64
69
|
def create_connection
|
65
70
|
raise NotImplementedError.new
|
66
71
|
end
|
67
|
-
|
72
|
+
|
68
73
|
def batch_insertable?
|
69
74
|
true
|
70
75
|
end
|
71
|
-
|
76
|
+
|
72
77
|
# Yields an available connection. Flushes the connection-pool and reconnects
|
73
78
|
# if the connection returns an error.
|
74
79
|
def connection
|
@@ -78,48 +83,49 @@ module DataMapper
|
|
78
83
|
rescue => execution_error
|
79
84
|
# Log error on failure
|
80
85
|
logger.error { execution_error }
|
81
|
-
|
86
|
+
|
82
87
|
# Close all open connections, assuming that if one
|
83
88
|
# had an error, it's likely due to a lost connection,
|
84
89
|
# in which case all connections are likely broken.
|
85
90
|
flush_connections!
|
86
|
-
|
91
|
+
|
87
92
|
raise execution_error
|
88
93
|
end
|
89
94
|
end
|
90
|
-
|
95
|
+
|
91
96
|
# Close any open connections.
|
92
|
-
def flush_connections!
|
93
|
-
|
94
|
-
|
97
|
+
def flush_connections!
|
98
|
+
@connection_pool.available_connections.each do |active_connection|
|
99
|
+
begin
|
95
100
|
active_connection.close
|
101
|
+
rescue => close_connection_error
|
102
|
+
# An error on closing the connection is almost expected
|
103
|
+
# if the socket is broken.
|
104
|
+
logger.warn { close_connection_error }
|
96
105
|
end
|
97
|
-
rescue => close_connection_error
|
98
|
-
# An error on closing the connection is almost expected
|
99
|
-
# if the socket is broken.
|
100
|
-
logger.warn { close_connection_error }
|
101
106
|
end
|
102
|
-
|
107
|
+
|
103
108
|
# Reopen fresh connections.
|
109
|
+
@connection_pool.instance_variable_set('@created_count', 0)
|
104
110
|
@connection_pool.available_connections.clear
|
105
111
|
end
|
106
|
-
|
112
|
+
|
107
113
|
def transaction(&block)
|
108
114
|
raise NotImplementedError.new
|
109
115
|
end
|
110
|
-
|
116
|
+
|
111
117
|
def query(*args)
|
112
118
|
db = create_connection
|
113
|
-
|
119
|
+
|
114
120
|
command = db.create_command(args.shift)
|
115
|
-
|
121
|
+
|
116
122
|
reader = command.execute_reader(*args)
|
117
123
|
fields = reader.fields.map { |field| Inflector.underscore(field).to_sym }
|
118
124
|
results = []
|
119
125
|
|
120
126
|
if fields.size > 1
|
121
127
|
struct = Struct.new(*fields)
|
122
|
-
|
128
|
+
|
123
129
|
reader.each do
|
124
130
|
results << struct.new(*reader.current_row)
|
125
131
|
end
|
@@ -136,8 +142,8 @@ module DataMapper
|
|
136
142
|
ensure
|
137
143
|
reader.close if reader
|
138
144
|
db.close
|
139
|
-
end
|
140
|
-
|
145
|
+
end
|
146
|
+
|
141
147
|
def execute(*args)
|
142
148
|
db = create_connection
|
143
149
|
command = db.create_command(args.shift)
|
@@ -148,15 +154,15 @@ module DataMapper
|
|
148
154
|
ensure
|
149
155
|
db.close
|
150
156
|
end
|
151
|
-
|
157
|
+
|
152
158
|
def handle_error(error)
|
153
159
|
raise error
|
154
160
|
end
|
155
|
-
|
161
|
+
|
156
162
|
def schema
|
157
163
|
@schema || ( @schema = self.class::Mappings::Schema.new(self, @configuration.database) )
|
158
164
|
end
|
159
|
-
|
165
|
+
|
160
166
|
def column_exists_for_table?(table_name, column_name)
|
161
167
|
connection do |db|
|
162
168
|
table = self.table(table_name)
|
@@ -166,15 +172,19 @@ module DataMapper
|
|
166
172
|
end
|
167
173
|
end
|
168
174
|
end
|
169
|
-
|
175
|
+
|
170
176
|
def delete(database_context, instance)
|
171
177
|
table = self.table(instance)
|
172
|
-
|
178
|
+
|
173
179
|
if instance.is_a?(Class)
|
174
180
|
table.delete_all!
|
175
181
|
else
|
176
182
|
callback(instance, :before_destroy)
|
177
|
-
|
183
|
+
|
184
|
+
table.associations.each do |association|
|
185
|
+
instance.send(association.name).deactivate unless association.is_a?(::DataMapper::Associations::BelongsToAssociation)
|
186
|
+
end
|
187
|
+
|
178
188
|
if table.paranoid?
|
179
189
|
instance.instance_variable_set(table.paranoid_column.instance_variable_name, Time::now)
|
180
190
|
instance.save
|
@@ -189,34 +199,37 @@ module DataMapper
|
|
189
199
|
database_context.identity_map.delete(instance)
|
190
200
|
callback(instance, :after_destroy)
|
191
201
|
end
|
192
|
-
end
|
202
|
+
end
|
193
203
|
end
|
194
204
|
end
|
195
|
-
|
196
|
-
def save(database_context, instance, validate = true)
|
205
|
+
|
206
|
+
def save(database_context, instance, validate = true, cleared = Set.new)
|
197
207
|
case instance
|
198
|
-
when Class then
|
208
|
+
when Class then
|
209
|
+
table(instance).create!
|
210
|
+
table(instance).activate_associations!
|
199
211
|
when Mappings::Table then instance.create!
|
200
|
-
when DataMapper::Persistence then
|
212
|
+
when DataMapper::Persistence then
|
201
213
|
event = instance.new_record? ? :create : :update
|
202
|
-
|
203
|
-
return false if validate && !instance.validate_recursively(event, Set.new)
|
204
|
-
|
214
|
+
|
215
|
+
return false if (validate && !instance.validate_recursively(event, Set.new)) || cleared.include?(instance)
|
216
|
+
cleared << instance
|
217
|
+
|
205
218
|
callback(instance, :before_save)
|
206
|
-
|
207
|
-
return
|
208
|
-
|
219
|
+
|
220
|
+
return true unless instance.new_record? || instance.dirty?
|
221
|
+
|
209
222
|
result = send(event, database_context, instance)
|
210
|
-
|
223
|
+
|
211
224
|
instance.database_context = database_context
|
212
225
|
instance.attributes.each_pair do |name, value|
|
213
226
|
instance.original_values[name] = value
|
214
227
|
end
|
215
|
-
|
228
|
+
|
216
229
|
instance.loaded_associations.each do |association|
|
217
|
-
association.save_without_validation(database_context) if association.dirty?
|
230
|
+
association.save_without_validation(database_context, cleared) if association.dirty?
|
218
231
|
end
|
219
|
-
|
232
|
+
|
220
233
|
callback(instance, :after_save)
|
221
234
|
result
|
222
235
|
end
|
@@ -224,41 +237,56 @@ module DataMapper
|
|
224
237
|
logger.error(error)
|
225
238
|
raise error
|
226
239
|
end
|
227
|
-
|
228
|
-
def save_without_validation(database_context, instance)
|
229
|
-
save(database_context, instance, false)
|
240
|
+
|
241
|
+
def save_without_validation(database_context, instance, cleared = Set.new)
|
242
|
+
save(database_context, instance, false, cleared)
|
230
243
|
end
|
231
|
-
|
244
|
+
|
232
245
|
def update(database_context, instance)
|
233
246
|
callback(instance, :before_update)
|
234
|
-
|
247
|
+
|
248
|
+
instance = update_magic_properties(database_context, instance)
|
249
|
+
|
235
250
|
table = self.table(instance)
|
236
251
|
attributes = instance.dirty_attributes
|
237
252
|
parameters = []
|
238
|
-
|
253
|
+
|
239
254
|
unless attributes.empty?
|
240
255
|
sql = "UPDATE " << table.to_sql << " SET "
|
241
|
-
|
256
|
+
|
242
257
|
sql << attributes.map do |key, value|
|
243
258
|
parameters << value
|
244
259
|
"#{table[key].to_sql} = ?"
|
245
260
|
end.join(', ')
|
246
|
-
|
261
|
+
|
247
262
|
sql << " WHERE #{table.key.to_sql} = ?"
|
248
263
|
parameters << instance.key
|
249
|
-
|
250
|
-
connection do |db|
|
251
|
-
db.create_command(sql).execute_non_query(*parameters)
|
252
|
-
|
264
|
+
|
265
|
+
result = connection do |db|
|
266
|
+
db.create_command(sql).execute_non_query(*parameters)
|
267
|
+
end
|
268
|
+
|
269
|
+
# BUG: do_mysql returns inaccurate affected row counts for UPDATE statements.
|
270
|
+
if true || result.to_i > 0
|
271
|
+
callback(instance, :after_update)
|
272
|
+
return true
|
273
|
+
else
|
274
|
+
return false
|
253
275
|
end
|
254
276
|
else
|
255
277
|
true
|
256
278
|
end
|
257
279
|
end
|
258
|
-
|
280
|
+
|
281
|
+
def empty_insert_sql
|
282
|
+
"DEFAULT VALUES"
|
283
|
+
end
|
284
|
+
|
259
285
|
def create(database_context, instance)
|
260
286
|
callback(instance, :before_create)
|
261
287
|
|
288
|
+
instance = update_magic_properties(database_context, instance)
|
289
|
+
|
262
290
|
table = self.table(instance)
|
263
291
|
attributes = instance.dirty_attributes
|
264
292
|
|
@@ -268,10 +296,12 @@ module DataMapper
|
|
268
296
|
attributes[:type] = instance.class.name
|
269
297
|
)
|
270
298
|
end
|
271
|
-
|
299
|
+
|
272
300
|
keys = []
|
273
301
|
values = []
|
274
302
|
attributes.each_pair do |key, value|
|
303
|
+
raise ArgumentError.new("#{value.inspect} is not a valid value for #{key.inspect}") if value.is_a?(Array)
|
304
|
+
|
275
305
|
keys << table[key].to_sql
|
276
306
|
values << value
|
277
307
|
end
|
@@ -279,37 +309,112 @@ module DataMapper
|
|
279
309
|
sql = if keys.size > 0
|
280
310
|
"INSERT INTO #{table.to_sql} (#{keys.join(', ')}) VALUES ?"
|
281
311
|
else
|
282
|
-
"INSERT INTO #{table.to_sql}"
|
312
|
+
"INSERT INTO #{table.to_sql} #{self.empty_insert_sql}"
|
313
|
+
end
|
314
|
+
|
315
|
+
result = connection do |db|
|
316
|
+
db.create_command(sql).execute_non_query(values)
|
317
|
+
end
|
318
|
+
|
319
|
+
if result.to_i > 0
|
320
|
+
instance.instance_variable_set(:@new_record, false)
|
321
|
+
instance.key = result.last_insert_row if table.key.serial? && !attributes.include?(table.key.name)
|
322
|
+
database_context.identity_map.set(instance)
|
323
|
+
callback(instance, :after_create)
|
324
|
+
return true
|
325
|
+
else
|
326
|
+
return false
|
283
327
|
end
|
284
|
-
|
285
|
-
|
286
|
-
|
328
|
+
end
|
329
|
+
|
330
|
+
MAGIC_PROPERTIES = {
|
331
|
+
:updated_at => lambda { self.updated_at = Time::now },
|
332
|
+
:updated_on => lambda { self.updated_on = Date::today },
|
333
|
+
:created_at => lambda { self.created_at ||= Time::now },
|
334
|
+
:created_on => lambda { self.created_on ||= Date::today }
|
335
|
+
}
|
336
|
+
|
337
|
+
def update_magic_properties(database_context, instance)
|
338
|
+
instance.class.properties.find_all { |property| MAGIC_PROPERTIES.has_key?(property.name) }.each do |property|
|
339
|
+
instance.instance_eval(&MAGIC_PROPERTIES[property.name])
|
287
340
|
end
|
288
|
-
instance
|
289
|
-
instance.key = insert_id if table.key.serial? && !attributes.include?(table.key.name)
|
290
|
-
database_context.identity_map.set(instance)
|
291
|
-
callback(instance, :after_create)
|
341
|
+
instance
|
292
342
|
end
|
293
|
-
|
343
|
+
|
294
344
|
def load(database_context, klass, options)
|
295
345
|
self.class::Commands::LoadCommand.new(self, database_context, klass, options).call
|
296
346
|
end
|
297
|
-
|
347
|
+
|
298
348
|
def get(database_context, klass, keys)
|
299
349
|
table = self.table(klass)
|
300
|
-
|
301
|
-
|
350
|
+
instance_id = table.key.type_cast_value(keys.first)
|
351
|
+
instance = database_context.identity_map.get(klass, instance_id)
|
352
|
+
|
353
|
+
return instance if instance
|
354
|
+
|
355
|
+
column_indexes = {}
|
356
|
+
select_columns = []
|
357
|
+
|
358
|
+
table.columns.each_with_index do |column, i|
|
359
|
+
column_indexes[column] = i
|
360
|
+
select_columns << column.to_sql
|
361
|
+
end
|
362
|
+
|
363
|
+
sql = "SELECT #{select_columns.join(', ')} FROM #{table.to_sql} WHERE #{table.keys.map { |key| "#{key.to_sql} = ?" }.join(' AND ')}"
|
364
|
+
|
302
365
|
connection do |db|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
366
|
+
reader = nil
|
367
|
+
begin
|
368
|
+
reader = db.create_command(sql).execute_reader(*keys)
|
369
|
+
|
370
|
+
if reader.has_rows?
|
371
|
+
|
372
|
+
instance_type = klass
|
373
|
+
|
374
|
+
if table.multi_class? && table.type_column
|
375
|
+
value = reader.item(column_indexes[table.type_column])
|
376
|
+
instance_type = table.type_column.type_cast_value(value) unless value.blank?
|
377
|
+
end
|
378
|
+
|
379
|
+
if instance.nil?
|
380
|
+
instance = instance_type.allocate()
|
381
|
+
instance.instance_variable_set(:@__key, instance_id)
|
382
|
+
instance.instance_variable_set(:@new_record, false)
|
383
|
+
database_context.identity_map.set(instance)
|
384
|
+
elsif instance.new_record?
|
385
|
+
instance.instance_variable_set(:@__key, instance_id)
|
386
|
+
instance.instance_variable_set(:@new_record, false)
|
387
|
+
database_context.identity_map.set(instance)
|
388
|
+
end
|
389
|
+
|
390
|
+
instance.database_context = database_context
|
391
|
+
|
392
|
+
instance_type.callbacks.execute(:before_materialize, instance)
|
393
|
+
|
394
|
+
originals = instance.original_values
|
395
|
+
|
396
|
+
column_indexes.each_pair do |column, i|
|
397
|
+
value = column.type_cast_value(reader.item(i))
|
398
|
+
instance.instance_variable_set(column.instance_variable_name, value)
|
399
|
+
|
400
|
+
case value
|
401
|
+
when String, Date, Time then originals[column.name] = value.dup
|
402
|
+
else originals[column.name] = value
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
instance.loaded_set = [instance]
|
407
|
+
|
408
|
+
instance_type.callbacks.execute(:after_materialize, instance)
|
409
|
+
end # if reader.has_rows?
|
410
|
+
ensure
|
411
|
+
reader.close if reader && reader.open?
|
309
412
|
end
|
310
|
-
end
|
413
|
+
end # connection
|
414
|
+
|
415
|
+
return instance
|
311
416
|
end
|
312
|
-
|
417
|
+
|
313
418
|
def table(instance)
|
314
419
|
case instance
|
315
420
|
when DataMapper::Adapters::Sql::Mappings::Table then instance
|
@@ -318,39 +423,40 @@ module DataMapper
|
|
318
423
|
else raise "Don't know how to map #{instance.inspect} to a table."
|
319
424
|
end
|
320
425
|
end
|
321
|
-
|
426
|
+
|
322
427
|
def callback(instance, callback_name)
|
323
428
|
instance.class.callbacks.execute(callback_name, instance)
|
324
429
|
end
|
325
|
-
|
430
|
+
|
326
431
|
# This callback copies and sub-classes modules and classes
|
327
432
|
# in the DoAdapter to the inherited class so you don't
|
328
433
|
# have to copy and paste large blocks of code from the
|
329
434
|
# DoAdapter.
|
330
|
-
#
|
435
|
+
#
|
331
436
|
# Basically, when inheriting from the DoAdapter, you
|
332
437
|
# aren't just inheriting a single class, you're inheriting
|
333
438
|
# a whole graph of Types. For convenience.
|
334
439
|
def self.inherited(base)
|
335
|
-
|
440
|
+
|
336
441
|
commands = base.const_set('Commands', Module.new)
|
337
442
|
|
338
443
|
Sql::Commands.constants.each do |name|
|
339
444
|
commands.const_set(name, Class.new(Sql::Commands.const_get(name)))
|
340
445
|
end
|
341
|
-
|
446
|
+
|
342
447
|
mappings = base.const_set('Mappings', Module.new)
|
343
|
-
|
448
|
+
|
344
449
|
Sql::Mappings.constants.each do |name|
|
345
450
|
mappings.const_set(name, Class.new(Sql::Mappings.const_get(name)))
|
346
451
|
end
|
347
|
-
|
452
|
+
|
348
453
|
base.const_set('TYPES', TYPES.dup)
|
349
454
|
base.const_set('FIND_OPTIONS', FIND_OPTIONS.dup)
|
350
|
-
|
455
|
+
base.const_set('SYNTAX', SYNTAX.dup)
|
456
|
+
|
351
457
|
super
|
352
458
|
end
|
353
|
-
|
459
|
+
|
354
460
|
TYPES = {
|
355
461
|
:integer => 'int'.freeze,
|
356
462
|
:string => 'varchar'.freeze,
|
@@ -367,8 +473,8 @@ module DataMapper
|
|
367
473
|
include Sql
|
368
474
|
include Quoting
|
369
475
|
include Coersion
|
370
|
-
|
476
|
+
|
371
477
|
end # class DoAdapter
|
372
|
-
|
478
|
+
|
373
479
|
end # module Adapters
|
374
|
-
end # module DataMapper
|
480
|
+
end # module DataMapper
|