datamapper 0.2.0 → 0.2.1
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 +20 -1
- data/environment.rb +5 -3
- data/lib/data_mapper.rb +42 -23
- data/lib/data_mapper/adapters/data_object_adapter.rb +76 -73
- data/lib/data_mapper/adapters/mysql_adapter.rb +15 -1
- data/lib/data_mapper/adapters/postgresql_adapter.rb +1 -1
- data/lib/data_mapper/adapters/sql/commands/load_command.rb +242 -28
- data/lib/data_mapper/adapters/sql/mappings/column.rb +18 -1
- data/lib/data_mapper/adapters/sql/mappings/table.rb +20 -32
- data/lib/data_mapper/adapters/sql/quoting.rb +40 -7
- data/lib/data_mapper/adapters/sqlite3_adapter.rb +7 -1
- data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +6 -1
- data/lib/data_mapper/associations/has_many_association.rb +1 -1
- data/lib/data_mapper/context.rb +4 -2
- data/lib/data_mapper/database.rb +2 -12
- data/lib/data_mapper/support/active_record_impersonation.rb +1 -1
- data/lib/data_mapper/support/blank.rb +2 -2
- data/lib/data_mapper/support/serialization.rb +60 -4
- data/lib/data_mapper/support/string.rb +24 -2
- data/lib/data_mapper/validations/validation_errors.rb +6 -3
- data/performance.rb +20 -5
- data/plugins/dataobjects/Rakefile +2 -0
- data/plugins/dataobjects/do.rb +18 -8
- data/plugins/dataobjects/do_mysql.rb +57 -24
- data/plugins/dataobjects/do_postgres.rb +3 -7
- data/plugins/dataobjects/do_sqlite3.rb +18 -17
- data/plugins/dataobjects/swig_mysql/Makefile +146 -0
- data/plugins/dataobjects/swig_mysql/extconf.rb +13 -1
- data/plugins/dataobjects/swig_mysql/mkmf.log +24 -0
- data/plugins/dataobjects/swig_mysql/mysql_c.bundle +0 -0
- data/plugins/dataobjects/swig_mysql/mysql_c.c +303 -2501
- data/plugins/dataobjects/swig_mysql/mysql_c.i +63 -4
- data/plugins/dataobjects/swig_mysql/mysql_c.o +0 -0
- data/profile_data_mapper.rb +4 -4
- data/rakefile.rb +13 -7
- data/spec/acts_as_tree_spec.rb +2 -0
- data/spec/associations_spec.rb +12 -0
- data/spec/attributes_spec.rb +2 -0
- data/spec/base_spec.rb +2 -0
- data/spec/callbacks_spec.rb +2 -0
- data/spec/can_has_sphinx.rb +0 -1
- data/spec/coersion_spec.rb +10 -3
- data/spec/column_spec.rb +23 -0
- data/spec/conditions_spec.rb +18 -18
- data/spec/count_command_spec.rb +2 -0
- data/spec/dataobjects_spec.rb +26 -0
- data/spec/delete_command_spec.rb +2 -0
- data/spec/embedded_value_spec.rb +2 -0
- data/spec/fixtures/people.yaml +1 -1
- data/spec/fixtures/posts.yaml +3 -0
- data/spec/legacy_spec.rb +2 -0
- data/spec/load_command_spec.rb +28 -2
- data/spec/magic_columns_spec.rb +2 -0
- data/spec/models/person.rb +1 -1
- data/spec/models/post.rb +8 -0
- data/spec/query_spec.rb +2 -0
- data/spec/save_command_spec.rb +2 -0
- data/spec/schema_spec.rb +2 -0
- data/spec/serialization_spec.rb +58 -0
- data/spec/single_table_inheritance_spec.rb +2 -0
- data/spec/symbolic_operators_spec.rb +2 -0
- data/spec/validates_confirmation_of_spec.rb +2 -0
- data/spec/validates_format_of_spec.rb +2 -0
- data/spec/validates_length_of_spec.rb +2 -0
- data/spec/validates_uniqueness_of_spec.rb +2 -0
- data/spec/validations_spec.rb +2 -0
- data/tasks/fixtures.rb +15 -10
- metadata +10 -13
- data/lib/data_mapper/adapters/sql/commands/conditions.rb +0 -130
- data/lib/data_mapper/adapters/sql/commands/loader.rb +0 -99
- data/plugins/dataobjects/swig_mysql/do_mysql.bundle +0 -0
- data/spec/conversions_to_yaml_spec.rb +0 -17
data/CHANGELOG
CHANGED
@@ -94,4 +94,23 @@
|
|
94
94
|
* Removed TableExistsCommand
|
95
95
|
* Session renamed to Context
|
96
96
|
* Most command implementations moved to methods in SqlAdapter
|
97
|
-
* Removed UnitOfWork module, instead moving a slightly refactored implementation into Base
|
97
|
+
* Removed UnitOfWork module, instead moving a slightly refactored implementation into Base
|
98
|
+
|
99
|
+
-- 0.2.1
|
100
|
+
* Added :float column support
|
101
|
+
* Added association proxies: ie: Zoo.first.exhibits.animals
|
102
|
+
* Columns stored in SortedSet
|
103
|
+
* Swig files are no longer RDOCed
|
104
|
+
* Added :date column support
|
105
|
+
* BUG: Fixed UTC issues with datetimes
|
106
|
+
* Added #to_yaml method
|
107
|
+
* Added #to_xml method
|
108
|
+
* Added #to_json method
|
109
|
+
* BUG: Fixed HasManyAssociation::Set#inspect
|
110
|
+
* BUG: Fixed #reload!
|
111
|
+
* BUG: Column copy for STI moved into Table#initialize to better handle STI with multiple mapped databases
|
112
|
+
* BUG: before_create callbacks moved in the execution flow since they weren't guaranteed to fire before
|
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!)
|
115
|
+
* Performance enhancements: (thanks wycats!)
|
116
|
+
|
data/environment.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
MERB_ROOT = Dir::pwd
|
2
|
+
MERB_ENV = 'development'
|
3
|
+
|
1
4
|
# Require the DataMapper, and a Mock Adapter.
|
2
5
|
require File.dirname(__FILE__) + '/lib/data_mapper'
|
3
6
|
require File.dirname(__FILE__) + '/spec/mock_adapter'
|
@@ -6,8 +9,7 @@ adapter = ENV['ADAPTER'] || 'sqlite3'
|
|
6
9
|
|
7
10
|
configuration_options = {
|
8
11
|
:adapter => adapter,
|
9
|
-
:database => (ENV['DATABASE'] || 'data_mapper_1').dup
|
10
|
-
:single_threaded => true
|
12
|
+
:database => (ENV['DATABASE'] || 'data_mapper_1').dup
|
11
13
|
}
|
12
14
|
|
13
15
|
# Prepare the log path, and remove the existing spec.log
|
@@ -42,4 +44,4 @@ DataMapper::Database.setup(:mock, :adapter => MockAdapter)
|
|
42
44
|
[:default, :mock].each { |name| database(name) { load_models.call } }
|
43
45
|
|
44
46
|
# Reset the test database.
|
45
|
-
DataMapper::Base.auto_migrate!
|
47
|
+
DataMapper::Base.auto_migrate! unless ENV['AUTO_MIGRATE'] == 'false'
|
data/lib/data_mapper.rb
CHANGED
@@ -9,10 +9,11 @@
|
|
9
9
|
|
10
10
|
# This line just let's us require anything in the +lib+ sub-folder
|
11
11
|
# without specifying a full path.
|
12
|
+
unless defined?(DM_PLUGINS_ROOT)
|
13
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
DM_PLUGINS_ROOT = (File.dirname(__FILE__) + '/../plugins')
|
15
|
+
DM_PLUGINS_ROOT = (File.dirname(__FILE__) + '/../plugins')
|
16
|
+
end
|
16
17
|
|
17
18
|
# Require the basics...
|
18
19
|
require 'yaml'
|
@@ -29,27 +30,45 @@ require 'data_mapper/base'
|
|
29
30
|
# This block of code is for compatibility with Ruby On Rails' or Merb's database.yml
|
30
31
|
# file, allowing you to simply require the data_mapper.rb in your
|
31
32
|
# Rails application's environment.rb to configure the DataMapper.
|
32
|
-
|
33
|
-
application_root, application_environment = *if defined?(MERB_ROOT)
|
34
|
-
|
35
|
-
elsif defined?(RAILS_ROOT)
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
DM_APP_ROOT = application_root || Dir::pwd
|
40
|
-
|
41
|
-
if application_root && File.exists?(application_root + '/config/database.yml')
|
33
|
+
unless defined?(DM_APP_ROOT)
|
34
|
+
application_root, application_environment = *if defined?(MERB_ROOT)
|
35
|
+
[MERB_ROOT, MERB_ENV]
|
36
|
+
elsif defined?(RAILS_ROOT)
|
37
|
+
[RAILS_ROOT, RAILS_ENV]
|
38
|
+
end
|
42
39
|
|
43
|
-
|
44
|
-
current_database_config = database_configurations[application_environment] || database_configurations[application_environment.to_sym]
|
40
|
+
DM_APP_ROOT = application_root || Dir::pwd
|
45
41
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
42
|
+
if application_root && File.exists?(application_root + '/config/database.yml')
|
43
|
+
|
44
|
+
database_configurations = YAML::load_file(application_root + '/config/database.yml')
|
45
|
+
current_database_config = database_configurations[application_environment] || database_configurations[application_environment.to_sym]
|
46
|
+
|
47
|
+
config = lambda { |key| current_database_config[key.to_s] || current_database_config[key] }
|
48
|
+
|
49
|
+
default_database_config = {
|
50
|
+
:adapter => config[:adapter],
|
51
|
+
:host => config[:host],
|
52
|
+
:database => config[:database],
|
53
|
+
:username => config[:username],
|
54
|
+
:password => config[:password]
|
55
|
+
}
|
53
56
|
|
54
|
-
|
57
|
+
DataMapper::Database.setup(default_database_config)
|
58
|
+
|
59
|
+
elsif application_root && FileTest.directory?(application_root + '/config')
|
60
|
+
|
61
|
+
%w(development testing production).map do |environment|
|
62
|
+
<<-EOS.margin
|
63
|
+
#{environment}:
|
64
|
+
adapter: mysql
|
65
|
+
username: root
|
66
|
+
password:
|
67
|
+
host: localhost
|
68
|
+
database: #{File.dirname(DM_APP_ROOT).split('/').last}_#{environment}
|
69
|
+
EOS
|
70
|
+
end
|
71
|
+
|
72
|
+
#File::open(application_root + '/config/database.yml')
|
73
|
+
end
|
55
74
|
end
|
@@ -49,10 +49,7 @@ module DataMapper
|
|
49
49
|
|
50
50
|
def initialize(configuration)
|
51
51
|
super
|
52
|
-
|
53
|
-
unless @configuration.single_threaded?
|
54
|
-
@connection_pool = Support::ConnectionPool.new { create_connection }
|
55
|
-
end
|
52
|
+
@connection_pool = Support::ConnectionPool.new(4) { create_connection }
|
56
53
|
end
|
57
54
|
|
58
55
|
def create_connection
|
@@ -64,11 +61,7 @@ module DataMapper
|
|
64
61
|
def connection
|
65
62
|
begin
|
66
63
|
# Yield the appropriate connection
|
67
|
-
|
68
|
-
yield(@active_connection || @active_connection = create_connection)
|
69
|
-
else
|
70
|
-
@connection_pool.hold { |active_connection| yield(active_connection) }
|
71
|
-
end
|
64
|
+
@connection_pool.hold { |active_connection| yield(active_connection) }
|
72
65
|
rescue => execution_error
|
73
66
|
# Log error on failure
|
74
67
|
@configuration.log.error(execution_error)
|
@@ -76,31 +69,28 @@ module DataMapper
|
|
76
69
|
# Close all open connections, assuming that if one
|
77
70
|
# had an error, it's likely due to a lost connection,
|
78
71
|
# in which case all connections are likely broken.
|
79
|
-
|
80
|
-
if @configuration.single_threaded?
|
81
|
-
@active_connection.close
|
82
|
-
else
|
83
|
-
@connection_pool.available_connections.each do |active_connection|
|
84
|
-
active_connection.close
|
85
|
-
end
|
86
|
-
end
|
87
|
-
rescue => close_connection_error
|
88
|
-
# An error on closing the connection is almost expected
|
89
|
-
# if the socket is broken.
|
90
|
-
@configuration.log.warn(close_connection_error)
|
91
|
-
end
|
92
|
-
|
93
|
-
# Reopen fresh connections.
|
94
|
-
if @configuration.single_threaded?
|
95
|
-
@active_connection = create_connection
|
96
|
-
else
|
97
|
-
@connection_pool.available_connections.clear
|
98
|
-
end
|
72
|
+
flush_connections!
|
99
73
|
|
100
74
|
raise execution_error
|
101
75
|
end
|
102
76
|
end
|
77
|
+
|
78
|
+
# Close any open connections.
|
79
|
+
def flush_connections!
|
80
|
+
begin
|
81
|
+
@connection_pool.available_connections.each do |active_connection|
|
82
|
+
active_connection.close
|
83
|
+
end
|
84
|
+
rescue => close_connection_error
|
85
|
+
# An error on closing the connection is almost expected
|
86
|
+
# if the socket is broken.
|
87
|
+
@configuration.log.warn(close_connection_error)
|
88
|
+
end
|
103
89
|
|
90
|
+
# Reopen fresh connections.
|
91
|
+
@connection_pool.available_connections.clear
|
92
|
+
end
|
93
|
+
|
104
94
|
def transaction(&block)
|
105
95
|
raise NotImplementedError.new
|
106
96
|
end
|
@@ -109,43 +99,45 @@ module DataMapper
|
|
109
99
|
connection do |db|
|
110
100
|
sql = escape_sql(*args)
|
111
101
|
log.debug { sql }
|
112
|
-
|
113
|
-
|
102
|
+
|
114
103
|
command = db.create_command(sql)
|
115
|
-
|
104
|
+
|
116
105
|
if block_given?
|
117
|
-
reader
|
118
|
-
result = yield(reader)
|
119
|
-
reader.close
|
106
|
+
command.execute_reader { |reader| yield(reader) }
|
120
107
|
else
|
121
|
-
|
108
|
+
command.execute_non_query
|
122
109
|
end
|
123
|
-
|
124
|
-
result
|
125
110
|
end
|
126
111
|
rescue => e
|
127
112
|
handle_error(e)
|
128
113
|
end
|
129
114
|
|
130
|
-
def query(*args)
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
115
|
+
def query(*args)
|
116
|
+
connection do |db|
|
117
|
+
sql = escape_sql(*args)
|
118
|
+
log.debug { sql }
|
119
|
+
|
120
|
+
command = db.create_command(sql)
|
121
|
+
|
122
|
+
command.execute_reader do |reader|
|
123
|
+
fields = reader.fields.map { |field| Inflector.underscore(field).to_sym }
|
138
124
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
125
|
+
results = []
|
126
|
+
|
127
|
+
if fields.size > 1
|
128
|
+
struct = Struct.new(*fields)
|
129
|
+
|
130
|
+
reader.each do
|
131
|
+
results << struct.new(*reader.current_row)
|
132
|
+
end
|
133
|
+
else
|
134
|
+
reader.each do
|
135
|
+
results << reader.item(0)
|
136
|
+
end
|
145
137
|
end
|
138
|
+
|
139
|
+
results
|
146
140
|
end
|
147
|
-
|
148
|
-
results
|
149
141
|
end
|
150
142
|
end
|
151
143
|
|
@@ -174,7 +166,13 @@ module DataMapper
|
|
174
166
|
end
|
175
167
|
|
176
168
|
def create_table(name)
|
177
|
-
|
169
|
+
table = self.table(name)
|
170
|
+
|
171
|
+
if table.exists?
|
172
|
+
false
|
173
|
+
else
|
174
|
+
execute(table.to_create_table_sql); true
|
175
|
+
end
|
178
176
|
end
|
179
177
|
|
180
178
|
def delete(session, instance)
|
@@ -202,46 +200,51 @@ module DataMapper
|
|
202
200
|
when DataMapper::Base then
|
203
201
|
return false unless instance.dirty? && instance.valid?
|
204
202
|
|
205
|
-
callback(instance, :before_save)
|
206
|
-
|
207
|
-
table = self.table(instance)
|
208
|
-
attributes = instance.dirty_attributes
|
203
|
+
callback(instance, :before_save)
|
209
204
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
# INSERT
|
214
|
-
result = if instance.new_record?
|
215
|
-
callback(instance, :before_create)
|
205
|
+
# INSERT
|
206
|
+
result = if instance.new_record?
|
207
|
+
callback(instance, :before_create)
|
216
208
|
|
209
|
+
table = self.table(instance)
|
210
|
+
attributes = instance.dirty_attributes
|
211
|
+
|
212
|
+
unless attributes.empty?
|
213
|
+
attributes[:type] = instance.class.name if table.multi_class?
|
214
|
+
|
217
215
|
keys = []
|
218
216
|
values = []
|
219
217
|
attributes.each_pair do |key, value|
|
220
218
|
keys << table[key].to_sql
|
221
219
|
values << value
|
222
220
|
end
|
223
|
-
|
221
|
+
|
224
222
|
# Formatting is a bit off here, but it looks nicer in the log this way.
|
225
223
|
insert_id = execute("INSERT INTO #{table.to_sql} (#{keys.join(', ')}) VALUES (#{values.map { |v| quote_value(v) }.join(', ')})").last_insert_row
|
226
224
|
instance.instance_variable_set(:@new_record, false)
|
227
225
|
instance.key = insert_id if table.key.serial? && !attributes.include?(table.key.name)
|
228
226
|
session.identity_map.set(instance)
|
229
227
|
callback(instance, :after_create)
|
230
|
-
|
231
|
-
|
232
|
-
|
228
|
+
end
|
229
|
+
# UPDATE
|
230
|
+
else
|
231
|
+
callback(instance, :before_update)
|
232
|
+
|
233
|
+
table = self.table(instance)
|
234
|
+
attributes = instance.dirty_attributes
|
233
235
|
|
236
|
+
unless attributes.empty?
|
234
237
|
sql = "UPDATE " << table.to_sql << " SET "
|
235
|
-
|
238
|
+
|
236
239
|
sql << attributes.map do |key, value|
|
237
240
|
"#{table[key].to_sql} = #{quote_value(value)}"
|
238
241
|
end.join(', ')
|
239
|
-
|
242
|
+
|
240
243
|
sql << " WHERE #{table.key.to_sql} = " << quote_value(instance.key)
|
241
|
-
|
244
|
+
|
242
245
|
execute(sql).to_i > 0 && callback(instance, :after_update)
|
243
246
|
end
|
244
|
-
end
|
247
|
+
end
|
245
248
|
|
246
249
|
instance.attributes.each_pair do |name, value|
|
247
250
|
instance.original_hashes[name] = value.hash
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'data_mapper/adapters/data_object_adapter'
|
2
2
|
begin
|
3
3
|
require 'do_mysql'
|
4
|
-
rescue
|
4
|
+
rescue LoadError
|
5
5
|
STDERR.puts <<-EOS
|
6
6
|
You must install the DataObjects::Mysql driver.
|
7
7
|
rake dm:install:mysql
|
@@ -24,6 +24,8 @@ module DataMapper
|
|
24
24
|
builder['dbname', :database]
|
25
25
|
builder['socket', :socket]
|
26
26
|
|
27
|
+
log.debug { connection_string.strip }
|
28
|
+
|
27
29
|
conn = DataObject::Mysql::Connection.new(connection_string.strip)
|
28
30
|
conn.open
|
29
31
|
cmd = conn.create_command("SET NAMES UTF8")
|
@@ -31,6 +33,18 @@ module DataMapper
|
|
31
33
|
return conn
|
32
34
|
end
|
33
35
|
|
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
|
+
|
34
48
|
module Mappings
|
35
49
|
|
36
50
|
def to_create_table_sql
|
@@ -1,26 +1,144 @@
|
|
1
|
-
require 'data_mapper/adapters/sql/commands/conditions'
|
2
|
-
require 'data_mapper/adapters/sql/commands/loader'
|
3
|
-
|
4
1
|
module DataMapper
|
5
2
|
module Adapters
|
6
3
|
module Sql
|
7
4
|
module Commands
|
8
5
|
|
9
6
|
class LoadCommand
|
10
|
-
|
7
|
+
|
8
|
+
class Loader
|
9
|
+
|
10
|
+
def initialize(load_command, klass)
|
11
|
+
@load_command, @klass = load_command, klass
|
12
|
+
@columns = {}
|
13
|
+
@key = nil
|
14
|
+
@key_index = nil
|
15
|
+
@type_override_present = false
|
16
|
+
@type_override_index = nil
|
17
|
+
@type_override = nil
|
18
|
+
@session = load_command.session
|
19
|
+
@reload = load_command.reload?
|
20
|
+
@set = []
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_column(column, index)
|
24
|
+
if column.key?
|
25
|
+
@key = column
|
26
|
+
@key_index = index
|
27
|
+
end
|
28
|
+
|
29
|
+
if column.type == :class
|
30
|
+
@type_override_present = true
|
31
|
+
@type_override_index = index
|
32
|
+
@type_override = column
|
33
|
+
end
|
34
|
+
|
35
|
+
@columns[index] = column
|
36
|
+
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def materialize(values)
|
41
|
+
|
42
|
+
instance_id = @key.type_cast_value(values[@key_index])
|
43
|
+
instance = if @type_override_present
|
44
|
+
create_instance(instance_id, @type_override.type_cast_value(values[@type_override_index]))
|
45
|
+
else
|
46
|
+
create_instance(instance_id)
|
47
|
+
end
|
48
|
+
|
49
|
+
@klass.callbacks.execute(:before_materialize, instance)
|
50
|
+
|
51
|
+
original_hashes = instance.original_hashes
|
52
|
+
|
53
|
+
@columns.each_pair do |index, column|
|
54
|
+
# This may be a little confusing, but we're
|
55
|
+
# setting both the original-hash value, and the
|
56
|
+
# instance-variable through method chaining to avoid
|
57
|
+
# lots of extra short-lived local variables.
|
58
|
+
original_hashes[column.name] = instance.instance_variable_set(
|
59
|
+
column.instance_variable_name,
|
60
|
+
column.type_cast_value(values[index])
|
61
|
+
).hash
|
62
|
+
end
|
63
|
+
|
64
|
+
instance.instance_variable_set(:@loaded_set, @set)
|
65
|
+
@set << instance
|
66
|
+
|
67
|
+
@klass.callbacks.execute(:after_materialize, instance)
|
68
|
+
|
69
|
+
return instance
|
70
|
+
end
|
71
|
+
|
72
|
+
def loaded_set
|
73
|
+
@set
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def create_instance(instance_id, instance_type = @klass)
|
79
|
+
instance = @session.identity_map.get(@klass, instance_id)
|
80
|
+
|
81
|
+
if instance.nil? || @reload
|
82
|
+
instance = instance_type.new() if instance.nil?
|
83
|
+
instance.instance_variable_set(:@__key, instance_id)
|
84
|
+
instance.instance_variable_set(:@new_record, false)
|
85
|
+
@session.identity_map.set(instance)
|
86
|
+
elsif instance.new_record?
|
87
|
+
instance.instance_variable_set(:@__key, instance_id)
|
88
|
+
instance.instance_variable_set(:@new_record, false)
|
89
|
+
end
|
90
|
+
|
91
|
+
instance.session = @session
|
92
|
+
|
93
|
+
return instance
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
class ConditionsError < StandardError
|
99
|
+
|
100
|
+
attr_reader :inner_error
|
101
|
+
|
102
|
+
def initialize(clause, value, inner_error)
|
103
|
+
@clause, @value, @inner_error = clause, value, inner_error
|
104
|
+
end
|
105
|
+
|
106
|
+
def message
|
107
|
+
"Conditions (:clause => #{@clause.inspect}, :value => #{@value.inspect}) failed: #{@inner_error}"
|
108
|
+
end
|
109
|
+
|
110
|
+
def backtrace
|
111
|
+
@inner_error.backtrace
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
11
116
|
attr_reader :conditions, :session, :options
|
12
117
|
|
13
118
|
def initialize(adapter, session, primary_class, options = {})
|
14
119
|
@adapter, @session, @primary_class = adapter, session, primary_class
|
15
120
|
|
16
|
-
|
121
|
+
# BEGIN: Partion out the options hash into general options,
|
122
|
+
# and conditions.
|
123
|
+
standard_find_options = @adapter.class::FIND_OPTIONS
|
124
|
+
conditions_hash = {}
|
125
|
+
@options = {}
|
126
|
+
|
127
|
+
options.each do |key,value|
|
128
|
+
if standard_find_options.include?(key) && key != :conditions
|
129
|
+
@options[key] = value
|
130
|
+
else
|
131
|
+
conditions_hash[key] = value
|
132
|
+
end
|
133
|
+
end
|
134
|
+
# END
|
17
135
|
|
18
136
|
@order = @options[:order]
|
19
137
|
@limit = @options[:limit]
|
20
138
|
@offset = @options[:offset]
|
21
139
|
@reload = @options[:reload]
|
22
140
|
@instance_id = conditions_hash[:id]
|
23
|
-
@conditions =
|
141
|
+
@conditions = parse_conditions(conditions_hash)
|
24
142
|
@loaders = Hash.new { |h,k| h[k] = Loader.new(self, k) }
|
25
143
|
end
|
26
144
|
|
@@ -75,7 +193,7 @@ module DataMapper
|
|
75
193
|
unless reload? || @instance_id.blank? || @instance_id.is_a?(Array)
|
76
194
|
# If the id is for only a single record, attempt to find it.
|
77
195
|
if instance = @session.identity_map.get(@primary_class, @instance_id)
|
78
|
-
return instance
|
196
|
+
return [instance]
|
79
197
|
end
|
80
198
|
end
|
81
199
|
|
@@ -92,11 +210,7 @@ module DataMapper
|
|
92
210
|
|
93
211
|
results += @loaders[@primary_class].loaded_set
|
94
212
|
|
95
|
-
|
96
|
-
results.first
|
97
|
-
else
|
98
|
-
results
|
99
|
-
end
|
213
|
+
return results
|
100
214
|
end
|
101
215
|
|
102
216
|
def load(reader)
|
@@ -120,6 +234,11 @@ module DataMapper
|
|
120
234
|
end
|
121
235
|
end
|
122
236
|
|
237
|
+
# Are any conditions present?
|
238
|
+
def conditions_empty?
|
239
|
+
@conditions.empty?
|
240
|
+
end
|
241
|
+
|
123
242
|
# Generate a select statement based on the initialization
|
124
243
|
# arguments.
|
125
244
|
def to_parameterized_sql
|
@@ -136,10 +255,29 @@ module DataMapper
|
|
136
255
|
sql << ' ' << association.to_shallow_sql
|
137
256
|
end
|
138
257
|
|
139
|
-
unless
|
140
|
-
|
141
|
-
|
142
|
-
|
258
|
+
unless conditions_empty?
|
259
|
+
sql << ' WHERE ('
|
260
|
+
|
261
|
+
last_index = @conditions.size
|
262
|
+
current_index = 0
|
263
|
+
|
264
|
+
@conditions.each do |condition|
|
265
|
+
case condition
|
266
|
+
when String then sql << condition
|
267
|
+
when Array then
|
268
|
+
sql << condition.shift
|
269
|
+
parameters += condition
|
270
|
+
else
|
271
|
+
raise "Unable to parse condition: #{condition.inspect}" if condition
|
272
|
+
end
|
273
|
+
|
274
|
+
if (current_index += 1) == last_index
|
275
|
+
sql << ')'
|
276
|
+
else
|
277
|
+
sql << ') AND ('
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end # unless conditions_empty?
|
143
281
|
|
144
282
|
unless @order.nil?
|
145
283
|
sql << ' ORDER BY ' << @order.to_s
|
@@ -156,9 +294,62 @@ module DataMapper
|
|
156
294
|
parameters.unshift(sql)
|
157
295
|
end
|
158
296
|
|
297
|
+
# If more than one table is involved in the query, the column definitions should
|
298
|
+
# be qualified by the table name. ie: people.name
|
299
|
+
# This method determines wether that needs to happen or not.
|
300
|
+
# Note: After the first call, the calculations are avoided by overwriting this
|
301
|
+
# method with a simple getter.
|
159
302
|
def qualify_columns?
|
160
|
-
return @qualify_columns unless @qualify_columns.nil?
|
161
303
|
@qualify_columns = !(included_associations.empty? && shallow_included_associations.empty?)
|
304
|
+
def self.qualify_columns?
|
305
|
+
@qualify_columns
|
306
|
+
end
|
307
|
+
@qualify_columns
|
308
|
+
end
|
309
|
+
|
310
|
+
# expression_to_sql takes a set of arguments, and turns them into a an
|
311
|
+
# Array of generated SQL, followed by optional Values to interpolate as SQL-Parameters.
|
312
|
+
#
|
313
|
+
# Parameters:
|
314
|
+
# +clause+ The name of the column as a Symbol, a raw-SQL String, a Mappings::Column
|
315
|
+
# instance, or a Symbol::Operator.
|
316
|
+
# +value+ The Value for the condition.
|
317
|
+
# +collector+ An Array representing all conditions that is appended to by expression_to_sql
|
318
|
+
#
|
319
|
+
# Returns: Undefined Output. The work performed is added to the +collector+ argument.
|
320
|
+
# Example:
|
321
|
+
# conditions = []
|
322
|
+
# expression_to_sql(:name, 'Bob', conditions)
|
323
|
+
# => +undefined return value+
|
324
|
+
# conditions.inspect
|
325
|
+
# => ["name = ?", 'Bob']
|
326
|
+
def expression_to_sql(clause, value, collector)
|
327
|
+
qualify_columns = qualify_columns?
|
328
|
+
|
329
|
+
case clause
|
330
|
+
when Symbol::Operator then
|
331
|
+
operator = case clause.type
|
332
|
+
when :gt then '>'
|
333
|
+
when :gte then '>='
|
334
|
+
when :lt then '<'
|
335
|
+
when :lte then '<='
|
336
|
+
when :not then inequality_operator(value)
|
337
|
+
when :eql then equality_operator(value)
|
338
|
+
when :like then equality_operator(value, 'LIKE')
|
339
|
+
when :in then equality_operator(value)
|
340
|
+
else raise ArgumentError.new('Operator type not supported')
|
341
|
+
end
|
342
|
+
collector << ["#{primary_class_table[clause].to_sql(qualify_columns)} #{operator} ?", value]
|
343
|
+
when Symbol then
|
344
|
+
collector << ["#{primary_class_table[clause].to_sql(qualify_columns)} #{equality_operator(value)} ?", value]
|
345
|
+
when String then
|
346
|
+
collector << [clause, value]
|
347
|
+
when Mappings::Column then
|
348
|
+
collector << ["#{clause.to_sql(qualify_columns)} #{equality_operator(value)} ?", value]
|
349
|
+
else raise "CAN HAS CRASH? #{clause.inspect}"
|
350
|
+
end
|
351
|
+
rescue => e
|
352
|
+
raise ConditionsError.new(clause, value, e)
|
162
353
|
end
|
163
354
|
|
164
355
|
private
|
@@ -277,19 +468,42 @@ module DataMapper
|
|
277
468
|
@primary_class_table || (@primary_class_table = @adapter.table(@primary_class))
|
278
469
|
end
|
279
470
|
|
280
|
-
def
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
471
|
+
def parse_conditions(conditions_hash)
|
472
|
+
collection = []
|
473
|
+
|
474
|
+
case x = conditions_hash.delete(:conditions)
|
475
|
+
when Array then
|
476
|
+
clause = x.shift
|
477
|
+
expression_to_sql(clause, x, collection)
|
478
|
+
when Hash then
|
479
|
+
x.each_pair do |key,value|
|
480
|
+
expression_to_sql(key, value, collection)
|
289
481
|
end
|
482
|
+
else
|
483
|
+
raise "Unable to parse conditions: #{x.inspect}" if x
|
484
|
+
end
|
485
|
+
|
486
|
+
conditions_hash.each_pair do |key,value|
|
487
|
+
expression_to_sql(key, value, collection)
|
488
|
+
end
|
489
|
+
|
490
|
+
collection
|
491
|
+
end
|
492
|
+
|
493
|
+
def equality_operator(value, default = '=')
|
494
|
+
case value
|
495
|
+
when NilClass then 'IS'
|
496
|
+
when Array then 'IN'
|
497
|
+
else default
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
def inequality_operator(value, default = '<>')
|
502
|
+
case value
|
503
|
+
when NilClass then 'IS NOT'
|
504
|
+
when Array then 'NOT IN'
|
505
|
+
else default
|
290
506
|
end
|
291
|
-
|
292
|
-
[ options_hash, conditions_hash ]
|
293
507
|
end
|
294
508
|
|
295
509
|
end # class LoadCommand
|