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/lib/data_mapper/property.rb
CHANGED
@@ -1,4 +1,310 @@
|
|
1
|
+
|
1
2
|
module DataMapper
|
3
|
+
|
4
|
+
# :include:/QUICKLINKS
|
5
|
+
#
|
6
|
+
# = Properties
|
7
|
+
# A model's properties are not derived from database structure.
|
8
|
+
# Instead, properties are declared inside it's model's class definition,
|
9
|
+
# which map to (or generate) fields in a database.
|
10
|
+
#
|
11
|
+
# Defining properties explicitly in a model has several advantages.
|
12
|
+
# It centralizes information about the model
|
13
|
+
# in a single location, rather than having to dig out migrations, xml,
|
14
|
+
# or other config files. It also provides the ability to use Ruby's
|
15
|
+
# access control functions. Finally, since Datamapper only cares about
|
16
|
+
# properties explicitly defined in your models, Datamappers plays well
|
17
|
+
# with legacy databases and shares databases easily with other
|
18
|
+
# applications.
|
19
|
+
#
|
20
|
+
# == Declaring Properties
|
21
|
+
# Inside your class, you call the property method for each property you want to add.
|
22
|
+
# The only two required arguments are the name and type, everything else is optional.
|
23
|
+
#
|
24
|
+
# class Post < DataMapper::Base
|
25
|
+
# property :title, :string, :nullable => false # Cannot be null
|
26
|
+
# property :publish, :boolen, :default => false # Default value for new records
|
27
|
+
# is false
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# == Limiting Access
|
31
|
+
# Property access control is uses the same terminology Ruby does. Properties are
|
32
|
+
# public by default, but can also be declared private or protected as needed
|
33
|
+
# (via the :accessor option).
|
34
|
+
#
|
35
|
+
# class Post < DataMapper::Base
|
36
|
+
# property :title, :string, :accessor => :private # Both reader and writer are private
|
37
|
+
# property :body, :text, :accessor => :protected # Both reader and writer are protected
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# Access control is also analogous to Ruby getters, setters, and accessors, and can
|
41
|
+
# be declared using :reader and :writer, in addition to :accessor.
|
42
|
+
#
|
43
|
+
# class Post < DataMapper::Base
|
44
|
+
# property :title, :string, :writer => :private # Only writer is private
|
45
|
+
# property :tags, :string, :reader => :protected # Only reader is protected
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# == Overriding Accessors
|
49
|
+
# The accessor for any property can be overridden in the same manner that Ruby class accessors
|
50
|
+
# can be. After the property is defined, just add your custom accessor:
|
51
|
+
#
|
52
|
+
# class Post < DataMapper::Base
|
53
|
+
# property :title, :string
|
54
|
+
#
|
55
|
+
# def title=(new_title)
|
56
|
+
# raise ArgumentError if new_title != 'Luke is Awesome'
|
57
|
+
# @title = new_title
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# == Lazy Loading
|
62
|
+
# By default, some properties are not loaded when an object is fetched in Datamapper.
|
63
|
+
# These lazily loaded properties are fetched on demand when their accessor is called
|
64
|
+
# for the first time (as it is often unnecessary to instantiate -every- property
|
65
|
+
# -every- time an object is loaded). For instance, text fields are lazy loading by
|
66
|
+
# default, although you can over-ride this behavior if you wish:
|
67
|
+
#
|
68
|
+
# Example:
|
69
|
+
#
|
70
|
+
# class Post < DataMapper::Base
|
71
|
+
# property :title, :string # Loads normally
|
72
|
+
# property :body, :text # Is lazily loaded by default
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# If you want to over-ride the lazy loading on any field you can set it to true or
|
76
|
+
# false with the :lazy option.
|
77
|
+
#
|
78
|
+
# class Post < DataMapper::Base
|
79
|
+
# property :title, :string # Loads normally
|
80
|
+
# property :body, :text, :lazy => false # The default is now over-ridden
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# Delaying the request for lazy-loaded attributes even applies to objects accessed through
|
84
|
+
# associations. In a sense, Datamapper anticipates that you will likely be iterating
|
85
|
+
# over objects in associations and rolls all of the load commands for lazy-loaded
|
86
|
+
# properties into one request from the database.
|
87
|
+
#
|
88
|
+
# Example:
|
89
|
+
#
|
90
|
+
# Widget[1].components # loads when the post object is pulled from database, by default
|
91
|
+
# Widget[1].components.first.body # loads the values for the body property on all objects in the
|
92
|
+
# association, rather than just this one.
|
93
|
+
#
|
94
|
+
# == Keys
|
95
|
+
# Properties can be declared as primary or natural keys on a table. By default,
|
96
|
+
# Datamapper will assume <tt>:id</tt> and create it if you don't have it.
|
97
|
+
# You can, however, declare a property as the primary key of the table:
|
98
|
+
#
|
99
|
+
# property :legacy_pk, :string, :key => true
|
100
|
+
#
|
101
|
+
# This is roughly equivalent to Activerecord's <tt>set_primary_key</tt>, though
|
102
|
+
# non-integer data types may be used, thus Datamapper supports natural keys.
|
103
|
+
# When a property is declared as a natural key, accessing the object using the
|
104
|
+
# indexer syntax <tt>Class[key]</tt> remains valid.
|
105
|
+
#
|
106
|
+
# User[1] when :id is the primary key on the users table
|
107
|
+
# User['bill'] when :name is the primary (natural) key on the users table
|
108
|
+
#
|
109
|
+
# == Inferred Validations
|
110
|
+
# When properties are declared with specific column restrictions, Datamapper
|
111
|
+
# will infer a few validation rules for values assigned to that property.
|
112
|
+
#
|
113
|
+
# property :title, :string, :length => 250
|
114
|
+
# # => infers 'validates_length_of :title, :minimum => 0, :maximum => 250'
|
115
|
+
#
|
116
|
+
# property :title, :string, :nullable => false
|
117
|
+
# # => infers 'validates_presence_of :title
|
118
|
+
#
|
119
|
+
# property :email, :string, :format => :email_address
|
120
|
+
# # => infers 'validates_format_of :email, :with => :email_address
|
121
|
+
#
|
122
|
+
# property :title, :string, :length => 255, :nullable => false
|
123
|
+
# # => infers both 'validates_length_of' as well as 'validates_presence_of'
|
124
|
+
# # better: property :title, :string, :length => 1..255
|
125
|
+
#
|
126
|
+
# For more information about validations, visit the Validatable documentation.
|
127
|
+
# == Embedded Values
|
128
|
+
# As an alternative to extraneous has_one relationships, consider using an
|
129
|
+
# EmbeddedValue.
|
130
|
+
#
|
131
|
+
# == Misc. Notes
|
132
|
+
# * Properties declared as strings will default to a length of 50, rather than 255
|
133
|
+
# (typical max varchar column size). To overload the default, pass
|
134
|
+
# <tt>:length => 255</tt> or <tt>:length => 0..255</tt>. Since Datamapper does
|
135
|
+
# not introspect for properties, this means that legacy database tables may need
|
136
|
+
# their <tt>:string</tt> columns defined with a <tt>:length</tt> so that DM does
|
137
|
+
# not inadvertantly truncate data.
|
138
|
+
# * You may declare a Property with the data-type of <tt>:class</tt>.
|
139
|
+
# see SingleTableInheritance for more on how to use <tt>:class</tt> columns.
|
2
140
|
class Property
|
141
|
+
|
142
|
+
# NOTE: check is only for psql, so maybe the postgres adapter should define
|
143
|
+
# its own property options. currently it will produce a warning tho since
|
144
|
+
# PROPERTY_OPTIONS is a constant
|
145
|
+
PROPERTY_OPTIONS = [
|
146
|
+
:public, :protected, :private, :accessor, :reader, :writer,
|
147
|
+
:lazy, :default, :nullable, :key, :serial, :column, :size, :length,
|
148
|
+
:format, :index, :check, :ordinal, :auto_validation
|
149
|
+
]
|
150
|
+
|
151
|
+
VISIBILITY_OPTIONS = [:public, :protected, :private]
|
152
|
+
|
153
|
+
def initialize(klass, name, type, options)
|
154
|
+
|
155
|
+
@klass, @name, @type, @options = klass, name.to_sym, type, options
|
156
|
+
@symbolized_name = name.to_s.sub(/\?$/, '').to_sym
|
157
|
+
|
158
|
+
validate_type!
|
159
|
+
validate_options!
|
160
|
+
determine_visibility!
|
161
|
+
|
162
|
+
database.schema[klass].add_column(@symbolized_name, @type, @options)
|
163
|
+
klass::ATTRIBUTES << @symbolized_name
|
164
|
+
|
165
|
+
create_getter!
|
166
|
+
create_setter!
|
167
|
+
auto_validations! unless @options[:auto_validation] == false
|
168
|
+
|
169
|
+
end
|
170
|
+
|
171
|
+
def validate_type! # :nodoc:
|
172
|
+
adapter_class = database.adapter.class
|
173
|
+
raise ArgumentError.new("#{@type.inspect} is not a supported type in the database adapter. Valid types are:\n #{adapter_class::TYPES.keys.inspect}") unless adapter_class::TYPES.has_key?(@type)
|
174
|
+
end
|
175
|
+
|
176
|
+
def validate_options! # :nodoc:
|
177
|
+
@options.each_pair do |k,v|
|
178
|
+
raise ArgumentError.new("#{k.inspect} is not a supported option in DataMapper::Property::PROPERTY_OPTIONS") unless PROPERTY_OPTIONS.include?(k)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def determine_visibility! # :nodoc:
|
183
|
+
@reader_visibility = @options[:reader] || @options[:accessor] || :public
|
184
|
+
@writer_visibility = @options[:writer] || @options[:accessor] || :public
|
185
|
+
@writer_visibility = :protected if @options[:protected]
|
186
|
+
@writer_visibility = :private if @options[:private]
|
187
|
+
raise(ArgumentError.new, "property visibility must be :public, :protected, or :private") unless VISIBILITY_OPTIONS.include?(@reader_visibility) && VISIBILITY_OPTIONS.include?(@writer_visibility)
|
188
|
+
end
|
189
|
+
|
190
|
+
# defines the getter for the property
|
191
|
+
def create_getter!
|
192
|
+
if lazy?
|
193
|
+
klass.class_eval <<-EOS
|
194
|
+
#{reader_visibility.to_s}
|
195
|
+
def #{name}
|
196
|
+
lazy_load!(#{name.inspect})
|
197
|
+
class << self;
|
198
|
+
attr_accessor #{name.inspect}
|
199
|
+
end
|
200
|
+
@#{name}
|
201
|
+
end
|
202
|
+
EOS
|
203
|
+
else
|
204
|
+
klass.class_eval <<-EOS
|
205
|
+
#{reader_visibility.to_s}
|
206
|
+
def #{name}
|
207
|
+
#{instance_variable_name}
|
208
|
+
end
|
209
|
+
EOS
|
210
|
+
end
|
211
|
+
if type == :boolean
|
212
|
+
klass.class_eval <<-EOS
|
213
|
+
#{reader_visibility.to_s}
|
214
|
+
def #{name.to_s.ensure_ends_with('?')}
|
215
|
+
#{instance_variable_name}
|
216
|
+
end
|
217
|
+
EOS
|
218
|
+
end
|
219
|
+
rescue SyntaxError
|
220
|
+
raise SyntaxError.new(column)
|
221
|
+
end
|
222
|
+
|
223
|
+
# defines the setter for the property
|
224
|
+
def create_setter!
|
225
|
+
if lazy?
|
226
|
+
klass.class_eval <<-EOS
|
227
|
+
#{writer_visibility.to_s}
|
228
|
+
def #{name}=(value)
|
229
|
+
class << self;
|
230
|
+
attr_accessor #{name.inspect}
|
231
|
+
end
|
232
|
+
@#{name} = value
|
233
|
+
end
|
234
|
+
EOS
|
235
|
+
else
|
236
|
+
klass.class_eval <<-EOS
|
237
|
+
#{writer_visibility.to_s}
|
238
|
+
def #{name}=(value)
|
239
|
+
#{instance_variable_name} = value
|
240
|
+
end
|
241
|
+
EOS
|
242
|
+
end
|
243
|
+
rescue SyntaxError
|
244
|
+
raise SyntaxError.new(column)
|
245
|
+
end
|
246
|
+
|
247
|
+
# NOTE: :length may also be used in place of :size
|
248
|
+
AUTO_VALIDATIONS = {
|
249
|
+
:nullable => lambda { |k,v| "validates_presence_of :#{k}" if v == false },
|
250
|
+
:size => lambda { |k,v| "validates_length_of :#{k}, " + (v.is_a?(Range) ? ":minimum => #{v.first}, :maximum => #{v.last}" : ":maximum => #{v}") },
|
251
|
+
:format => lambda { |k, v| "validates_format_of :#{k}, :with => #{v.inspect}" }
|
252
|
+
}
|
253
|
+
|
254
|
+
AUTO_VALIDATIONS[:length] = AUTO_VALIDATIONS[:size].dup
|
255
|
+
|
256
|
+
# defines the inferred validations given a property definition.
|
257
|
+
def auto_validations!
|
258
|
+
AUTO_VALIDATIONS.each do |key, value|
|
259
|
+
next unless options.has_key?(key)
|
260
|
+
validation = value.call(name, options[key])
|
261
|
+
next if validation.empty?
|
262
|
+
klass.class_eval <<-EOS
|
263
|
+
begin
|
264
|
+
#{validation}
|
265
|
+
rescue ArgumentError => e
|
266
|
+
throw e unless e.message =~ /specify a unique key/
|
267
|
+
end
|
268
|
+
EOS
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def klass
|
273
|
+
@klass
|
274
|
+
end
|
275
|
+
|
276
|
+
def column
|
277
|
+
column = database.table(klass)[@name]
|
278
|
+
raise StandardError.new("#{@name.inspect} is not a valid column name") unless column
|
279
|
+
return column
|
280
|
+
end
|
281
|
+
|
282
|
+
def name
|
283
|
+
@name
|
284
|
+
end
|
285
|
+
|
286
|
+
def instance_variable_name # :nodoc:
|
287
|
+
column.instance_variable_name
|
288
|
+
end
|
289
|
+
|
290
|
+
def type
|
291
|
+
column.type
|
292
|
+
end
|
293
|
+
|
294
|
+
def options
|
295
|
+
column.options
|
296
|
+
end
|
297
|
+
|
298
|
+
def reader_visibility # :nodoc:
|
299
|
+
@reader_visibility
|
300
|
+
end
|
301
|
+
|
302
|
+
def writer_visibility # :nodoc:
|
303
|
+
@writer_visibility
|
304
|
+
end
|
305
|
+
|
306
|
+
def lazy?
|
307
|
+
column.lazy?
|
308
|
+
end
|
3
309
|
end
|
4
310
|
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module DataMapper
|
2
|
+
|
3
|
+
# This class handles option parsing and SQL generation.
|
4
|
+
class Query
|
5
|
+
|
6
|
+
# These are the standard finder options
|
7
|
+
OPTIONS = [
|
8
|
+
:select, :offset, :limit, :include, :reload, :conditions, :join, :order, :after_row_materialization
|
9
|
+
]
|
10
|
+
|
11
|
+
def initialize(adapter, klass, options = {})
|
12
|
+
# Set some of the standard options
|
13
|
+
@adapter, @klass = adapter, klass
|
14
|
+
@from = @adapter.table(@klass)
|
15
|
+
@parameters = []
|
16
|
+
@joins = []
|
17
|
+
|
18
|
+
# Parse simple options
|
19
|
+
@limit = options.fetch(:limit, nil)
|
20
|
+
@offset = options.fetch(:offset, nil)
|
21
|
+
@reload = options.fetch(:reload, false)
|
22
|
+
@order = options.fetch(:order, nil)
|
23
|
+
@after_row_materialization = options.fetch(:after_row_materialization, nil)
|
24
|
+
|
25
|
+
# Parse :include option
|
26
|
+
@includes = case include_options = options[:include]
|
27
|
+
when Array then include_options.dup
|
28
|
+
when Symbol then [include_options]
|
29
|
+
when NilClass then []
|
30
|
+
else raise ArgumentError.new(":include must be an Array, Symbol or nil, but was #{include_options.inspect}")
|
31
|
+
end
|
32
|
+
|
33
|
+
# Include lazy columns if specified in :include option
|
34
|
+
@columns = @from.columns.select do |column|
|
35
|
+
!column.lazy? || @includes.delete(column.name)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Qualify columns with their table name if joins are present
|
39
|
+
@qualify = !@includes.empty?
|
40
|
+
|
41
|
+
# Generate SQL for joins
|
42
|
+
@includes.each do |association_name|
|
43
|
+
association = @from.associations[association_name]
|
44
|
+
@joins << association.to_sql
|
45
|
+
@columns += association.associated_table.columns.select do |column|
|
46
|
+
!column.lazy?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Prepare conditions for parsing
|
51
|
+
@conditions = []
|
52
|
+
|
53
|
+
# Each non-standard option is assumed to be a column
|
54
|
+
options.each_pair do |k,v|
|
55
|
+
unless OPTIONS.include?(k)
|
56
|
+
append_condition(k, v)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# If a :conditions option is present, parse it
|
61
|
+
if conditions_option = options[:conditions]
|
62
|
+
if conditions_option.is_a?(String)
|
63
|
+
@conditions << conditions_option
|
64
|
+
else
|
65
|
+
append_condition(*conditions_option)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# If the table is paranoid, add a filter to the conditions
|
70
|
+
if @from.paranoid?
|
71
|
+
@conditions << "#{@from.paranoid_column.to_sql(qualify?)} IS NULL OR #{@from.paranoid_column.to_sql(qualify?)} > #{@adapter.class::SYNTAX[:now]}"
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
# SQL for query
|
77
|
+
def to_sql
|
78
|
+
sql = "SELECT #{columns.map { |column| column.to_sql(qualify?) }.join(', ')} FROM #{from.to_sql}"
|
79
|
+
|
80
|
+
sql << " " << joins.join($/) unless joins.empty?
|
81
|
+
sql << " WHERE (#{conditions.join(") AND (")})" unless conditions.empty?
|
82
|
+
return sql
|
83
|
+
end
|
84
|
+
|
85
|
+
# Parameters for query
|
86
|
+
def parameters
|
87
|
+
@parameters
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# Conditions for the query, in the form of an Array of Strings
|
93
|
+
def conditions
|
94
|
+
@conditions
|
95
|
+
end
|
96
|
+
|
97
|
+
# Determines wether columns should be qualified with their table-names.
|
98
|
+
def qualify?
|
99
|
+
@qualify
|
100
|
+
end
|
101
|
+
|
102
|
+
# The primary table in the FROM clause of the query
|
103
|
+
def from
|
104
|
+
@from
|
105
|
+
end
|
106
|
+
|
107
|
+
# SQL for any joins in the query
|
108
|
+
def joins
|
109
|
+
@joins
|
110
|
+
end
|
111
|
+
|
112
|
+
# Column mappings to be selected
|
113
|
+
def columns
|
114
|
+
@columns
|
115
|
+
end
|
116
|
+
|
117
|
+
def append_condition(clause, value)
|
118
|
+
case clause
|
119
|
+
when Symbol::Operator then
|
120
|
+
operator = case clause.type
|
121
|
+
when :gt then '>'
|
122
|
+
when :gte then '>='
|
123
|
+
when :lt then '<'
|
124
|
+
when :lte then '<='
|
125
|
+
when :not then inequality_operator(value)
|
126
|
+
when :eql then equality_operator(value)
|
127
|
+
when :like then equality_operator(value, 'LIKE')
|
128
|
+
when :in then equality_operator(value)
|
129
|
+
else raise ArgumentError.new('Operator type not supported')
|
130
|
+
end
|
131
|
+
@conditions << "#{from[clause].to_sql(qualify?)} #{operator} ?"
|
132
|
+
@parameters << value
|
133
|
+
when Symbol then
|
134
|
+
@conditions << "#{from[clause].to_sql(qualify?)} #{equality_operator(value)} ?"
|
135
|
+
@parameters << value
|
136
|
+
when String then
|
137
|
+
@conditions << clause
|
138
|
+
value.each { |v| @parameters << v }
|
139
|
+
when Mappings::Column then
|
140
|
+
@conditions << "#{clause.to_sql(qualify?)} #{equality_operator(value)} ?"
|
141
|
+
@parameters << value
|
142
|
+
else raise "CAN HAS CRASH? #{clause.inspect}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def equality_operator(value, default = '=')
|
147
|
+
case value
|
148
|
+
when NilClass then 'IS'
|
149
|
+
when Array then 'IN'
|
150
|
+
else default
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def inequality_operator(value, default = '<>')
|
155
|
+
case value
|
156
|
+
when NilClass then 'IS NOT'
|
157
|
+
when Array then 'NOT IN'
|
158
|
+
else default
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
end # class Query
|
164
|
+
end # module DataMapper
|