datamapper 0.2.4 → 0.2.5
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 +16 -1
- data/README +10 -8
- data/environment.rb +1 -1
- data/example.rb +13 -5
- data/lib/data_mapper.rb +2 -0
- data/lib/data_mapper/adapters/abstract_adapter.rb +61 -0
- data/lib/data_mapper/adapters/data_object_adapter.rb +33 -6
- data/lib/data_mapper/adapters/mysql_adapter.rb +5 -0
- data/lib/data_mapper/adapters/postgresql_adapter.rb +12 -0
- data/lib/data_mapper/adapters/sql/commands/load_command.rb +6 -14
- data/lib/data_mapper/adapters/sql/mappings/column.rb +37 -26
- data/lib/data_mapper/adapters/sql/mappings/table.rb +50 -8
- data/lib/data_mapper/associations.rb +4 -5
- data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +2 -2
- data/lib/data_mapper/associations/has_many_association.rb +40 -13
- data/lib/data_mapper/associations/has_n_association.rb +1 -1
- data/lib/data_mapper/base.rb +5 -448
- data/lib/data_mapper/callbacks.rb +12 -2
- data/lib/data_mapper/context.rb +4 -0
- data/lib/data_mapper/database.rb +1 -1
- data/lib/data_mapper/identity_map.rb +2 -2
- data/lib/data_mapper/persistence.rb +538 -0
- data/lib/data_mapper/support/active_record_impersonation.rb +21 -3
- data/lib/data_mapper/support/errors.rb +2 -0
- data/lib/data_mapper/support/serialization.rb +7 -10
- data/lib/data_mapper/support/struct.rb +7 -0
- data/lib/data_mapper/validatable_extensions/validations/validates_uniqueness_of.rb +11 -4
- data/performance.rb +23 -10
- data/rakefile.rb +1 -1
- data/spec/active_record_impersonation_spec.rb +2 -6
- data/spec/acts_as_tree_spec.rb +3 -1
- data/spec/associations_spec.rb +40 -160
- data/spec/attributes_spec.rb +1 -1
- data/spec/base_spec.rb +41 -13
- data/spec/callbacks_spec.rb +32 -0
- data/spec/coersion_spec.rb +1 -1
- data/spec/column_spec.rb +22 -12
- data/spec/dependency_spec.rb +5 -3
- data/spec/embedded_value_spec.rb +33 -17
- data/spec/has_many_association_spec.rb +173 -0
- data/spec/legacy_spec.rb +2 -2
- data/spec/load_command_spec.rb +59 -7
- data/spec/models/animal.rb +6 -2
- data/spec/models/animals_exhibit.rb +3 -1
- data/spec/models/career.rb +2 -1
- data/spec/models/comment.rb +3 -1
- data/spec/models/exhibit.rb +3 -1
- data/spec/models/fruit.rb +3 -1
- data/spec/models/person.rb +10 -1
- data/spec/models/post.rb +3 -1
- data/spec/models/project.rb +3 -1
- data/spec/models/section.rb +3 -1
- data/spec/models/serializer.rb +3 -1
- data/spec/models/user.rb +3 -1
- data/spec/models/zoo.rb +3 -1
- data/spec/paranoia_spec.rb +3 -1
- data/spec/postgres_spec.rb +54 -0
- data/spec/save_command_spec.rb +9 -5
- data/spec/schema_spec.rb +0 -91
- data/spec/single_table_inheritance_spec.rb +8 -0
- data/spec/table_spec.rb +46 -0
- data/spec/validates_uniqueness_of_spec.rb +19 -1
- metadata +8 -10
- data/lib/data_mapper/associations/has_one_association.rb +0 -77
- data/plugins/dataobjects/do_rb +0 -0
@@ -8,7 +8,7 @@ module DataMapper
|
|
8
8
|
|
9
9
|
class Table
|
10
10
|
|
11
|
-
attr_reader :klass, :name
|
11
|
+
attr_reader :klass, :name, :indexes, :composite_indexes
|
12
12
|
|
13
13
|
def initialize(adapter, klass_or_name)
|
14
14
|
raise "\"klass_or_name\" must not be nil!" if klass_or_name.nil?
|
@@ -28,8 +28,7 @@ module DataMapper
|
|
28
28
|
@paranoid = false
|
29
29
|
@paranoid_column = nil
|
30
30
|
|
31
|
-
if @klass && @klass.
|
32
|
-
|
31
|
+
if @klass && @klass.respond_to?(:persistent?) && @klass.superclass.respond_to?(:persistent?)
|
33
32
|
super_table = @adapter.table(@klass.superclass)
|
34
33
|
|
35
34
|
super_table.columns.each do |column|
|
@@ -120,6 +119,8 @@ module DataMapper
|
|
120
119
|
else
|
121
120
|
@adapter.connection do |db|
|
122
121
|
db.create_command(to_create_sql).execute_non_query
|
122
|
+
index_queries = to_create_index_sql + to_create_composite_index_sql
|
123
|
+
index_queries.each { |q| db.create_command(q).execute_non_query }
|
123
124
|
schema << self
|
124
125
|
true
|
125
126
|
end
|
@@ -189,7 +190,7 @@ module DataMapper
|
|
189
190
|
class << self
|
190
191
|
attr_accessor :key
|
191
192
|
end
|
192
|
-
|
193
|
+
Persistence::dependencies.resolve!
|
193
194
|
|
194
195
|
self.key
|
195
196
|
end
|
@@ -199,8 +200,21 @@ module DataMapper
|
|
199
200
|
@keys = @columns.select { |column| column.key? }
|
200
201
|
end
|
201
202
|
end
|
202
|
-
|
203
|
-
def
|
203
|
+
|
204
|
+
def indexes
|
205
|
+
@indexes || begin
|
206
|
+
@indexes = @columns.select { |column| column.index? }
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Add a composite index to the table.
|
211
|
+
# +index_columns+ should be an array including each column name.
|
212
|
+
def add_composite_index(index_columns = [], unique = false)
|
213
|
+
@composite_indexes ||= []
|
214
|
+
@composite_indexes << [index_columns, unique] # add paired tuple with the index
|
215
|
+
end
|
216
|
+
|
217
|
+
def add_column(column_name, type, options = {})
|
204
218
|
|
205
219
|
column_ordinal = if options.is_a?(Hash) && options.has_key?(:ordinal)
|
206
220
|
options.delete(:ordinal)
|
@@ -238,8 +252,8 @@ module DataMapper
|
|
238
252
|
elsif @klass_or_name.kind_of?(String)
|
239
253
|
@klass_or_name
|
240
254
|
elsif @klass_or_name.kind_of?(Class)
|
241
|
-
|
242
|
-
|
255
|
+
persistent_ancestor = @klass_or_name.superclass.respond_to?(:persistent?)
|
256
|
+
if @klass_or_name.superclass.respond_to?(:persistent?)
|
243
257
|
@adapter.table(@klass_or_name.superclass).name
|
244
258
|
else
|
245
259
|
Inflector.tableize(@klass_or_name.name)
|
@@ -297,10 +311,38 @@ module DataMapper
|
|
297
311
|
sql << ", PRIMARY KEY (#{keys.map { |c| c.to_sql }.join(', ') })"
|
298
312
|
end
|
299
313
|
sql << ")"
|
314
|
+
|
300
315
|
sql.compress_lines
|
301
316
|
end
|
302
317
|
end
|
303
318
|
|
319
|
+
# Returns an array with each separate CREATE INDEX statement
|
320
|
+
def to_create_index_sql
|
321
|
+
queries = []
|
322
|
+
unless indexes.blank?
|
323
|
+
indexes.each do |column|
|
324
|
+
sql = "CREATE INDEX #{to_s.downcase}_#{column}_index ON "
|
325
|
+
sql << "#{to_sql} (#{column.to_sql})"
|
326
|
+
queries << sql.compress_lines
|
327
|
+
end
|
328
|
+
end
|
329
|
+
queries
|
330
|
+
end
|
331
|
+
|
332
|
+
# Returns an array with each separate CREATE INDEX statement
|
333
|
+
def to_create_composite_index_sql
|
334
|
+
queries = []
|
335
|
+
unless composite_indexes.blank?
|
336
|
+
composite_indexes.each do |columns, unique|
|
337
|
+
sql = "CREATE #{unique ? 'UNIQUE ' : ''}INDEX "
|
338
|
+
sql << "#{to_s.downcase}_#{columns.join('_')}_index ON "
|
339
|
+
sql << "#{to_sql} (#{columns.join(', ')})"
|
340
|
+
queries << sql.compress_lines
|
341
|
+
end
|
342
|
+
end
|
343
|
+
queries
|
344
|
+
end
|
345
|
+
|
304
346
|
def to_drop_sql
|
305
347
|
@to_drop_sql || @to_drop_sql = "DROP TABLE #{to_sql}"
|
306
348
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'data_mapper/associations/reference'
|
2
2
|
require 'data_mapper/associations/has_many_association'
|
3
3
|
require 'data_mapper/associations/belongs_to_association'
|
4
|
-
require 'data_mapper/associations/has_one_association'
|
5
4
|
require 'data_mapper/associations/has_and_belongs_to_many_association'
|
6
5
|
|
7
6
|
module DataMapper
|
@@ -17,6 +16,10 @@ module DataMapper
|
|
17
16
|
database.schema[self].associations << HasManyAssociation.new(self, association_name, options)
|
18
17
|
end
|
19
18
|
|
19
|
+
def has_one(association_name, options = {})
|
20
|
+
database.schema[self].associations << HasManyAssociation.new(self, association_name, options)
|
21
|
+
end
|
22
|
+
|
20
23
|
def belongs_to(association_name, options = {})
|
21
24
|
database.schema[self].associations << BelongsToAssociation.new(self, association_name, options)
|
22
25
|
end
|
@@ -25,10 +28,6 @@ module DataMapper
|
|
25
28
|
database.schema[self].associations << HasAndBelongsToManyAssociation.new(self, association_name, options)
|
26
29
|
end
|
27
30
|
|
28
|
-
def has_one(association_name, options = {})
|
29
|
-
database.schema[self].associations << HasOneAssociation.new(self, association_name, options)
|
30
|
-
end
|
31
|
-
|
32
31
|
end
|
33
32
|
|
34
33
|
end
|
@@ -184,7 +184,7 @@ module DataMapper
|
|
184
184
|
command = db.create_command(association.to_delete_sql)
|
185
185
|
command.execute_non_query(@instance.key)
|
186
186
|
end
|
187
|
-
|
187
|
+
|
188
188
|
unless @entries.empty?
|
189
189
|
if adapter.batch_insertable?
|
190
190
|
sql = association.to_insert_sql
|
@@ -224,7 +224,7 @@ module DataMapper
|
|
224
224
|
|
225
225
|
def <<(member)
|
226
226
|
@new_members = true
|
227
|
-
entries << member
|
227
|
+
entries << member unless member.nil?
|
228
228
|
end
|
229
229
|
|
230
230
|
def clear
|
@@ -22,6 +22,14 @@ module DataMapper
|
|
22
22
|
"UPDATE #{associated_table.to_sql} SET #{foreign_key_column.to_sql} = NULL WHERE #{foreign_key_column.to_sql} = ?"
|
23
23
|
end
|
24
24
|
|
25
|
+
def instance_variable_name
|
26
|
+
class << self
|
27
|
+
attr_reader :instance_variable_name
|
28
|
+
end
|
29
|
+
|
30
|
+
@instance_variable_name = "@#{@association_name}"
|
31
|
+
end
|
32
|
+
|
25
33
|
class Set < Associations::Reference
|
26
34
|
|
27
35
|
include Enumerable
|
@@ -60,7 +68,7 @@ module DataMapper
|
|
60
68
|
end
|
61
69
|
|
62
70
|
def <<(associated_item)
|
63
|
-
items << associated_item
|
71
|
+
(@items || @items = []) << associated_item
|
64
72
|
|
65
73
|
# TODO: Optimize!
|
66
74
|
fk = association.foreign_key_column
|
@@ -85,8 +93,12 @@ module DataMapper
|
|
85
93
|
item
|
86
94
|
end
|
87
95
|
|
88
|
-
def set(
|
89
|
-
|
96
|
+
def set(value)
|
97
|
+
values = value.is_a?(Enumerable) ? value : [value]
|
98
|
+
@items = []
|
99
|
+
values.each do |item|
|
100
|
+
self << item
|
101
|
+
end
|
90
102
|
end
|
91
103
|
|
92
104
|
def method_missing(symbol, *args, &block)
|
@@ -100,6 +112,8 @@ module DataMapper
|
|
100
112
|
end
|
101
113
|
end
|
102
114
|
results.flatten
|
115
|
+
elsif items.size == 1 && items.first.respond_to?(symbol)
|
116
|
+
items.first.send(symbol, *args, &block)
|
103
117
|
else
|
104
118
|
super
|
105
119
|
end
|
@@ -117,17 +131,12 @@ module DataMapper
|
|
117
131
|
@items || begin
|
118
132
|
if @instance.loaded_set.nil?
|
119
133
|
@items = []
|
120
|
-
else
|
121
|
-
|
122
|
-
|
123
|
-
finder_options = { association.foreign_key_column.to_sym => @instance.loaded_set.map { |item| item.key } }
|
124
|
-
finder_options.merge!(association.finder_options)
|
125
|
-
|
126
|
-
associated_items = @instance.database_context.all(
|
127
|
-
association.associated_constant,
|
128
|
-
finder_options
|
129
|
-
).group_by { |entry| entry.send(fk) }
|
134
|
+
else
|
135
|
+
associated_items = fetch_sets
|
130
136
|
|
137
|
+
# This is where @items is set, by calling association=,
|
138
|
+
# which in turn calls HasManyAssociation::Set#set.
|
139
|
+
association_ivar_name = association.instance_variable_name
|
131
140
|
setter_method = "#{@association_name}=".to_sym
|
132
141
|
@instance.loaded_set.each do |entry|
|
133
142
|
entry.send(setter_method, associated_items[entry.key])
|
@@ -141,6 +150,24 @@ module DataMapper
|
|
141
150
|
def inspect
|
142
151
|
entries.inspect
|
143
152
|
end
|
153
|
+
|
154
|
+
def ==(other)
|
155
|
+
(items.size == 1 ? items.first : items) == other
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
def fetch_sets
|
160
|
+
finder_options = { association.foreign_key_column.to_sym => @instance.loaded_set.map { |item| item.key } }
|
161
|
+
finder_options.merge!(association.finder_options)
|
162
|
+
|
163
|
+
foreign_key_ivar_name = association.foreign_key_column.instance_variable_name
|
164
|
+
|
165
|
+
@instance.database_context.all(
|
166
|
+
association.associated_constant,
|
167
|
+
finder_options
|
168
|
+
).group_by { |entry| entry.instance_variable_get(foreign_key_ivar_name) }
|
169
|
+
end
|
170
|
+
|
144
171
|
end
|
145
172
|
|
146
173
|
end
|
@@ -23,7 +23,7 @@ module DataMapper
|
|
23
23
|
|
24
24
|
define_accessor(klass)
|
25
25
|
|
26
|
-
|
26
|
+
Persistence::dependencies.add(associated_constant_name) do |klass|
|
27
27
|
@foreign_key_column = associated_table[foreign_key_name]
|
28
28
|
|
29
29
|
unless @foreign_key_column
|
data/lib/data_mapper/base.rb
CHANGED
@@ -1,12 +1,4 @@
|
|
1
|
-
require 'data_mapper/
|
2
|
-
require 'data_mapper/support/active_record_impersonation'
|
3
|
-
require 'data_mapper/support/serialization'
|
4
|
-
require 'data_mapper/validations'
|
5
|
-
require 'data_mapper/associations'
|
6
|
-
require 'data_mapper/callbacks'
|
7
|
-
require 'data_mapper/embedded_value'
|
8
|
-
require 'data_mapper/auto_migrations'
|
9
|
-
require 'data_mapper/dependency_queue'
|
1
|
+
require 'data_mapper/persistence'
|
10
2
|
|
11
3
|
begin
|
12
4
|
require 'ferret'
|
@@ -16,449 +8,14 @@ end
|
|
16
8
|
module DataMapper
|
17
9
|
|
18
10
|
class Base
|
19
|
-
|
20
|
-
# This probably needs to be protected
|
21
|
-
attr_accessor :loaded_set
|
22
|
-
|
23
|
-
include CallbacksHelper
|
24
|
-
include Support::ActiveRecordImpersonation
|
25
|
-
include Support::Serialization
|
26
|
-
include Validations
|
27
|
-
include Associations
|
28
|
-
|
29
|
-
# Track classes that inherit from DataMapper::Base.
|
30
|
-
def self.subclasses
|
31
|
-
@subclasses || (@subclasses = [])
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.auto_migrate!
|
35
|
-
subclasses.each do |subclass|
|
36
|
-
subclass.auto_migrate!
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.dependencies
|
41
|
-
@dependency_queue || (@dependency_queue = DependencyQueue.new)
|
42
|
-
end
|
11
|
+
include DataMapper::Persistence
|
43
12
|
|
44
13
|
def self.inherited(klass)
|
45
|
-
klass
|
46
|
-
|
47
|
-
klass.send :extend, AutoMigrations
|
48
|
-
DataMapper::Base::subclasses << klass
|
49
|
-
klass.send(:undef_method, :id)
|
50
|
-
|
51
|
-
# When this class is sub-classed, copy the declared columns.
|
52
|
-
klass.class_eval do
|
53
|
-
def self.subclasses
|
54
|
-
@subclasses || (@subclasses = [])
|
55
|
-
end
|
56
|
-
|
57
|
-
def self.inherited(subclass)
|
58
|
-
|
59
|
-
super_table = database.table(self)
|
60
|
-
|
61
|
-
if super_table.type_column.nil?
|
62
|
-
super_table.add_column(:type, :class, {})
|
63
|
-
end
|
64
|
-
|
65
|
-
self::subclasses << subclass
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def self.logger
|
71
|
-
database.logger
|
72
|
-
end
|
73
|
-
|
74
|
-
def logger
|
75
|
-
self.class.logger
|
76
|
-
end
|
77
|
-
|
78
|
-
def self.transaction
|
79
|
-
yield
|
80
|
-
end
|
81
|
-
|
82
|
-
def self.properties
|
83
|
-
@properties
|
84
|
-
end
|
85
|
-
|
86
|
-
# Allows you to override the table name for a model.
|
87
|
-
# EXAMPLE:
|
88
|
-
# class WorkItem
|
89
|
-
# set_table_name 't_work_item_list'
|
90
|
-
# end
|
91
|
-
def self.set_table_name(value)
|
92
|
-
database.schema[self].name = value
|
93
|
-
end
|
94
|
-
|
95
|
-
def initialize(details = nil)
|
96
|
-
|
97
|
-
case details
|
98
|
-
when Hash then self.attributes = details
|
99
|
-
when DataMapper::Base then self.attributes = details.attributes
|
100
|
-
when NilClass then nil
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
PROPERTY_OPTIONS = [
|
105
|
-
:public, :protected, :private, :accessor, :reader, :writer,
|
106
|
-
:lazy, :default, :nullable, :key, :serial, :column
|
107
|
-
]
|
108
|
-
|
109
|
-
# Adds property accessors for a field that you'd like to be able to modify. The DataMapper doesn't
|
110
|
-
# use the table schema to infer accessors, you must explicity call #property to add field accessors
|
111
|
-
# to your model.
|
112
|
-
#
|
113
|
-
# EXAMPLE:
|
114
|
-
# class CellProvider
|
115
|
-
# property :name, :string
|
116
|
-
# property :rating, :integer
|
117
|
-
# end
|
118
|
-
#
|
119
|
-
# att = CellProvider.new(:name => 'AT&T')
|
120
|
-
# att.rating = 3
|
121
|
-
# puts att.name, att.rating
|
122
|
-
#
|
123
|
-
# => AT&T
|
124
|
-
# => 3
|
125
|
-
#
|
126
|
-
# OPTIONS:
|
127
|
-
# * <tt>lazy</tt>: Lazy load the specified property (:lazy => true). False by default.
|
128
|
-
# * <tt>accessor</tt>: Set method visibility for the property accessors. Affects both
|
129
|
-
# reader and writer. Allowable values are :public, :protected, :private. Defaults to
|
130
|
-
# :public
|
131
|
-
# * <tt>reader</tt>: Like the accessor option but affects only the property reader.
|
132
|
-
# * <tt>writer</tt>: Like the accessor option but affects only the property writer.
|
133
|
-
# * <tt>protected</tt>: Alias for :reader => :public, :writer => :protected
|
134
|
-
# * <tt>private</tt>: Alias for :reader => :public, :writer => :private
|
135
|
-
def self.property(name, type, options = {})
|
136
|
-
|
137
|
-
options.each_pair do |k,v|
|
138
|
-
raise ArgumentError.new("#{k.inspect} is not a supported option in DataMapper::Base::PROPERTY_OPTIONS") unless PROPERTY_OPTIONS.include?(k)
|
139
|
-
end
|
140
|
-
|
141
|
-
visibility_options = [:public, :protected, :private]
|
142
|
-
reader_visibility = options[:reader] || options[:accessor] || :public
|
143
|
-
writer_visibility = options[:writer] || options[:accessor] || :public
|
144
|
-
writer_visibility = :protected if options[:protected]
|
145
|
-
writer_visibility = :private if options[:private]
|
146
|
-
|
147
|
-
raise(ArgumentError.new, "property visibility must be :public, :protected, or :private") unless visibility_options.include?(reader_visibility) && visibility_options.include?(writer_visibility)
|
148
|
-
|
149
|
-
mapping = database.schema[self].add_column(name.to_s.sub(/\?$/, '').to_sym, type, options)
|
150
|
-
|
151
|
-
property_getter(mapping, reader_visibility)
|
152
|
-
property_setter(mapping, writer_visibility)
|
153
|
-
|
154
|
-
if MAGIC_PROPERTIES.has_key?(name)
|
155
|
-
class_eval(&MAGIC_PROPERTIES[name])
|
156
|
-
end
|
157
|
-
|
158
|
-
return name
|
159
|
-
end
|
160
|
-
|
161
|
-
MAGIC_PROPERTIES = {
|
162
|
-
:updated_at => lambda { before_save { |x| x.updated_at = Time::now } },
|
163
|
-
:updated_on => lambda { before_save { |x| x.updated_on = Date::today } },
|
164
|
-
:created_at => lambda { before_create { |x| x.created_at = Time::now } },
|
165
|
-
:created_on => lambda { before_create { |x| x.created_on = Date::today } }
|
166
|
-
}
|
167
|
-
|
168
|
-
# An embedded value maps the values of an object to fields in the record of the object's owner.
|
169
|
-
# #embed takes a symbol to define the embedded class, options, and an optional block. See
|
170
|
-
# examples for use cases.
|
171
|
-
#
|
172
|
-
# EXAMPLE:
|
173
|
-
# class CellPhone < DataMapper::Base
|
174
|
-
# property :number, :string
|
175
|
-
#
|
176
|
-
# embed :owner, :prefix => true do
|
177
|
-
# property :name, :string
|
178
|
-
# property :address, :string
|
179
|
-
# end
|
180
|
-
# end
|
181
|
-
#
|
182
|
-
# my_phone = CellPhone.new
|
183
|
-
# my_phone.owner.name = "Nick"
|
184
|
-
# puts my_phone.owner.name
|
185
|
-
#
|
186
|
-
# => Nick
|
187
|
-
#
|
188
|
-
# OPTIONS:
|
189
|
-
# * <tt>prefix</tt>: define a column prefix, so instead of mapping :address to an 'address'
|
190
|
-
# column, it would map to 'owner_address' in the example above. If :prefix => true is
|
191
|
-
# specified, the prefix will be the name of the symbol given as the first parameter. If the
|
192
|
-
# prefix is a string the specified string will be used for the prefix.
|
193
|
-
# * <tt>lazy</tt>: lazy-load all embedded values at the same time. :lazy => true to enable.
|
194
|
-
# Disabled (false) by default.
|
195
|
-
# * <tt>accessor</tt>: Set method visibility for all embedded properties. Affects both
|
196
|
-
# reader and writer. Allowable values are :public, :protected, :private. Defaults to :public
|
197
|
-
# * <tt>reader</tt>: Like the accessor option but affects only embedded property readers.
|
198
|
-
# * <tt>writer</tt>: Like the accessor option but affects only embedded property writers.
|
199
|
-
# * <tt>protected</tt>: Alias for :reader => :public, :writer => :protected
|
200
|
-
# * <tt>private</tt>: Alias for :reader => :public, :writer => :private
|
201
|
-
#
|
202
|
-
def self.embed(name, options = {}, &block)
|
203
|
-
EmbeddedValue::define(self, name, options, &block)
|
204
|
-
end
|
205
|
-
|
206
|
-
def self.property_getter(mapping, visibility = :public)
|
207
|
-
if mapping.lazy?
|
208
|
-
class_eval <<-EOS
|
209
|
-
#{visibility.to_s}
|
210
|
-
def #{mapping.name}
|
211
|
-
lazy_load!(#{mapping.name.inspect})
|
212
|
-
class << self;
|
213
|
-
attr_accessor #{mapping.name.inspect}
|
214
|
-
end
|
215
|
-
@#{mapping.name}
|
216
|
-
end
|
217
|
-
EOS
|
218
|
-
else
|
219
|
-
class_eval("#{visibility.to_s}; def #{mapping.name}; #{mapping.instance_variable_name} end") unless [ :public, :private, :protected ].include?(mapping.name)
|
220
|
-
end
|
221
|
-
|
222
|
-
if mapping.type == :boolean
|
223
|
-
class_eval("#{visibility.to_s}; def #{mapping.name}?; #{mapping.instance_variable_name} end")
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
def self.property_setter(mapping, visibility = :public)
|
228
|
-
if mapping.lazy?
|
229
|
-
class_eval <<-EOS
|
230
|
-
#{visibility.to_s}
|
231
|
-
def #{mapping.name}=(value)
|
232
|
-
class << self;
|
233
|
-
attr_accessor #{mapping.name.inspect}
|
234
|
-
end
|
235
|
-
@#{mapping.name} = value
|
236
|
-
end
|
237
|
-
EOS
|
238
|
-
else
|
239
|
-
class_eval("#{visibility.to_s}; def #{mapping.name}=(value); #{mapping.instance_variable_name} = value end")
|
240
|
-
end
|
241
|
-
end
|
242
|
-
|
243
|
-
# Lazy-loads the attributes for a loaded_set, then overwrites the accessors
|
244
|
-
# for the named methods so that the lazy_loading is skipped the second time.
|
245
|
-
def lazy_load!(*names)
|
246
|
-
|
247
|
-
names = names.map { |name| name.to_sym }.reject { |name| lazy_loaded_attributes.include?(name) }
|
248
|
-
|
249
|
-
reset_attribute = lambda do |instance|
|
250
|
-
singleton_class = (class << instance; self end)
|
251
|
-
names.each do |name|
|
252
|
-
instance.lazy_loaded_attributes << name
|
253
|
-
singleton_class.send(:attr_accessor, name)
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
unless names.empty? || new_record? || loaded_set.nil?
|
258
|
-
|
259
|
-
key = database_context.table(self.class).key.to_sym
|
260
|
-
keys_to_select = loaded_set.map do |instance|
|
261
|
-
instance.send(key)
|
262
|
-
end
|
263
|
-
|
264
|
-
database_context.all(
|
265
|
-
self.class,
|
266
|
-
:select => ([key] + names),
|
267
|
-
:reload => true,
|
268
|
-
key => keys_to_select
|
269
|
-
).each(&reset_attribute)
|
270
|
-
else
|
271
|
-
reset_attribute[self]
|
272
|
-
end
|
273
|
-
|
274
|
-
end
|
275
|
-
|
276
|
-
def new_record?
|
277
|
-
@new_record.nil? || @new_record
|
278
|
-
end
|
279
|
-
|
280
|
-
def ==(other)
|
281
|
-
private_attributes == other.send("private_attributes")
|
282
|
-
end
|
283
|
-
|
284
|
-
# Returns the difference between two objects, in terms of their attributes.
|
285
|
-
def ^(other)
|
286
|
-
results = {}
|
287
|
-
|
288
|
-
self_attributes, other_attributes = attributes, other.attributes
|
289
|
-
|
290
|
-
self_attributes.each_pair do |k,v|
|
291
|
-
other_value = other_attributes[k]
|
292
|
-
unless v == other_value
|
293
|
-
results[k] = [v, other_value]
|
294
|
-
end
|
295
|
-
end
|
296
|
-
|
297
|
-
results
|
298
|
-
end
|
299
|
-
|
300
|
-
def lazy_loaded_attributes
|
301
|
-
@lazy_loaded_attributes || @lazy_loaded_attributes = Set.new
|
302
|
-
end
|
303
|
-
|
304
|
-
def loaded_attributes
|
305
|
-
pairs = {}
|
306
|
-
|
307
|
-
database_context.table(self).columns.each do |column|
|
308
|
-
pairs[column.name] = instance_variable_get(column.instance_variable_name)
|
309
|
-
end
|
310
|
-
|
311
|
-
pairs
|
312
|
-
end
|
313
|
-
|
314
|
-
def update_attributes(update_hash)
|
315
|
-
self.attributes = update_hash
|
316
|
-
self.save
|
317
|
-
end
|
318
|
-
|
319
|
-
def attributes
|
320
|
-
pairs = {}
|
321
|
-
|
322
|
-
database_context.table(self).columns.each do |column|
|
323
|
-
if self.class.public_method_defined?(column.name)
|
324
|
-
lazy_load!(column.name) if column.lazy?
|
325
|
-
value = instance_variable_get(column.instance_variable_name)
|
326
|
-
pairs[column.name] = column.type == :class ? value.to_s : value
|
327
|
-
end
|
328
|
-
end
|
329
|
-
|
330
|
-
pairs
|
331
|
-
end
|
332
|
-
|
333
|
-
# Mass-assign mapped fields.
|
334
|
-
def attributes=(values_hash)
|
335
|
-
table = database_context.table(self.class)
|
336
|
-
|
337
|
-
values_hash.delete_if do |key, value|
|
338
|
-
!self.class.public_method_defined?("#{key}=")
|
339
|
-
end.each_pair do |key, value|
|
340
|
-
if respond_to?(key)
|
341
|
-
send("#{key}=", value)
|
342
|
-
elsif column = table[key]
|
343
|
-
instance_variable_set(column.instance_variable_name, value)
|
344
|
-
end
|
345
|
-
end
|
346
|
-
end
|
347
|
-
|
348
|
-
def dirty?
|
349
|
-
result = database_context.table(self).columns.any? do |column|
|
350
|
-
if column.type == :object
|
351
|
-
Marshal.dump(self.instance_variable_get(column.instance_variable_name)) != original_values[column.name]
|
352
|
-
else
|
353
|
-
self.instance_variable_get(column.instance_variable_name) != original_values[column.name]
|
354
|
-
end
|
355
|
-
end
|
356
|
-
|
357
|
-
return true if result
|
358
|
-
|
359
|
-
loaded_associations.any? do |loaded_association|
|
360
|
-
loaded_association.dirty?
|
361
|
-
end
|
14
|
+
DataMapper::Persistence::prepare_for_persistence(klass)
|
362
15
|
end
|
363
16
|
|
364
|
-
def
|
365
|
-
|
366
|
-
|
367
|
-
database_context.table(self).columns.each do |column|
|
368
|
-
value = instance_variable_get(column.instance_variable_name)
|
369
|
-
if value != original_values[column.name] && (!new_record? || !column.serial?)
|
370
|
-
pairs[column.name] = column.type != :object ? value : YAML.dump(value)
|
371
|
-
end
|
372
|
-
end
|
373
|
-
|
374
|
-
pairs
|
375
|
-
end
|
376
|
-
|
377
|
-
def original_values
|
378
|
-
@original_values || (@original_values = {})
|
379
|
-
end
|
380
|
-
|
381
|
-
def self.index
|
382
|
-
@index || @index = Ferret::Index::Index.new(:path => "#{database.adapter.index_path}/#{name}")
|
383
|
-
end
|
384
|
-
|
385
|
-
def self.reindex!
|
386
|
-
all.each do |record|
|
387
|
-
index << record.attributes
|
388
|
-
end
|
389
|
-
end
|
390
|
-
|
391
|
-
def self.search(phrase)
|
392
|
-
ids = []
|
393
|
-
|
394
|
-
query = "#{database.schema[self].columns.map(&:name).join('|')}:\"#{phrase}\""
|
395
|
-
|
396
|
-
index.search_each(query) do |document_id, score|
|
397
|
-
ids << index[document_id][:id]
|
398
|
-
end
|
399
|
-
return all(:id => ids)
|
400
|
-
end
|
401
|
-
|
402
|
-
def self.foreign_key
|
403
|
-
Inflector.underscore(self.name) + "_id"
|
404
|
-
end
|
405
|
-
|
406
|
-
def self.table
|
407
|
-
database.schema[self]
|
408
|
-
end
|
409
|
-
|
410
|
-
def inspect
|
411
|
-
inspected_attributes = attributes.map { |k,v| "@#{k}=#{v.inspect}" }
|
412
|
-
|
413
|
-
instance_variables.each do |name|
|
414
|
-
if instance_variable_get(name).kind_of?(Associations::HasManyAssociation)
|
415
|
-
inspected_attributes << "#{name}=#{instance_variable_get(name).inspect}"
|
416
|
-
end
|
417
|
-
end
|
418
|
-
|
419
|
-
"#<%s:0x%x @new_record=%s, %s>" % [self.class.name, (object_id * 2), new_record?, inspected_attributes.join(', ')]
|
420
|
-
end
|
421
|
-
|
422
|
-
def loaded_associations
|
423
|
-
@loaded_associations || @loaded_associations = []
|
424
|
-
end
|
425
|
-
|
426
|
-
def database_context=(value)
|
427
|
-
@database_context = value
|
428
|
-
end
|
429
|
-
|
430
|
-
def database_context
|
431
|
-
@database_context || ( @database_context = database )
|
432
|
-
end
|
433
|
-
|
434
|
-
def key=(value)
|
435
|
-
key_column = database_context.table(self.class).key
|
436
|
-
@__key = key_column.type_cast_value(value)
|
437
|
-
instance_variable_set(key_column.instance_variable_name, @__key)
|
438
|
-
end
|
439
|
-
|
440
|
-
def key
|
441
|
-
@__key || @__key = begin
|
442
|
-
key_column = database_context.table(self.class).key
|
443
|
-
key_column.type_cast_value(instance_variable_get(key_column.instance_variable_name))
|
444
|
-
end
|
445
|
-
end
|
446
|
-
|
447
|
-
private
|
448
|
-
|
449
|
-
# return all attributes, regardless of their visibility
|
450
|
-
def private_attributes
|
451
|
-
pairs = {}
|
452
|
-
|
453
|
-
database_context.table(self).columns.each do |column|
|
454
|
-
lazy_load!(column.name) if column.lazy?
|
455
|
-
value = instance_variable_get(column.instance_variable_name)
|
456
|
-
pairs[column.name] = column.type == :class ? value.to_s : value
|
457
|
-
end
|
458
|
-
|
459
|
-
pairs
|
17
|
+
def self.subclasses
|
18
|
+
[]
|
460
19
|
end
|
461
|
-
|
462
20
|
end
|
463
|
-
|
464
21
|
end
|