massive_record 0.2.1 → 0.2.2.rc1
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.md +58 -2
- data/Gemfile.lock +17 -17
- data/README.md +98 -41
- data/lib/massive_record.rb +2 -1
- data/lib/massive_record/adapters/thrift/hbase/hbase.rb +2425 -2154
- data/lib/massive_record/adapters/thrift/hbase/hbase_constants.rb +3 -3
- data/lib/massive_record/adapters/thrift/hbase/hbase_types.rb +195 -195
- data/lib/massive_record/adapters/thrift/row.rb +35 -4
- data/lib/massive_record/adapters/thrift/table.rb +49 -12
- data/lib/massive_record/orm/attribute_methods.rb +77 -5
- data/lib/massive_record/orm/attribute_methods/cast_numbers_on_write.rb +24 -0
- data/lib/massive_record/orm/attribute_methods/dirty.rb +18 -0
- data/lib/massive_record/orm/attribute_methods/time_zone_conversion.rb +24 -3
- data/lib/massive_record/orm/attribute_methods/write.rb +8 -1
- data/lib/massive_record/orm/base.rb +62 -8
- data/lib/massive_record/orm/column.rb +7 -11
- data/lib/massive_record/orm/default_id.rb +1 -1
- data/lib/massive_record/orm/embedded.rb +66 -0
- data/lib/massive_record/orm/errors.rb +17 -0
- data/lib/massive_record/orm/finders.rb +124 -71
- data/lib/massive_record/orm/finders/rescue_missing_table_on_find.rb +1 -1
- data/lib/massive_record/orm/finders/scope.rb +58 -34
- data/lib/massive_record/orm/id_factory.rb +22 -105
- data/lib/massive_record/orm/id_factory/atomic_incrementation.rb +117 -0
- data/lib/massive_record/orm/id_factory/timestamp.rb +60 -0
- data/lib/massive_record/orm/identity_map.rb +256 -0
- data/lib/massive_record/orm/log_subscriber.rb +18 -0
- data/lib/massive_record/orm/observer.rb +69 -0
- data/lib/massive_record/orm/persistence.rb +47 -119
- data/lib/massive_record/orm/persistence/operations.rb +100 -0
- data/lib/massive_record/orm/persistence/operations/atomic_operation.rb +71 -0
- data/lib/massive_record/orm/persistence/operations/destroy.rb +17 -0
- data/lib/massive_record/orm/persistence/operations/embedded/destroy.rb +26 -0
- data/lib/massive_record/orm/persistence/operations/embedded/insert.rb +27 -0
- data/lib/massive_record/orm/persistence/operations/embedded/operation_helpers.rb +66 -0
- data/lib/massive_record/orm/persistence/operations/embedded/reload.rb +39 -0
- data/lib/massive_record/orm/persistence/operations/embedded/update.rb +29 -0
- data/lib/massive_record/orm/persistence/operations/insert.rb +19 -0
- data/lib/massive_record/orm/persistence/operations/reload.rb +26 -0
- data/lib/massive_record/orm/persistence/operations/suppress.rb +15 -0
- data/lib/massive_record/orm/persistence/operations/table_operation_helpers.rb +106 -0
- data/lib/massive_record/orm/persistence/operations/update.rb +25 -0
- data/lib/massive_record/orm/query_instrumentation.rb +26 -49
- data/lib/massive_record/orm/raw_data.rb +47 -0
- data/lib/massive_record/orm/relations.rb +4 -0
- data/lib/massive_record/orm/relations/interface.rb +134 -0
- data/lib/massive_record/orm/relations/metadata.rb +58 -12
- data/lib/massive_record/orm/relations/proxy.rb +17 -12
- data/lib/massive_record/orm/relations/proxy/embedded_in.rb +54 -0
- data/lib/massive_record/orm/relations/proxy/embedded_in_polymorphic.rb +15 -0
- data/lib/massive_record/orm/relations/proxy/embeds_many.rb +215 -0
- data/lib/massive_record/orm/relations/proxy/references_many.rb +112 -88
- data/lib/massive_record/orm/relations/proxy/references_one.rb +1 -1
- data/lib/massive_record/orm/relations/proxy/references_one_polymorphic.rb +1 -1
- data/lib/massive_record/orm/relations/proxy_collection.rb +84 -0
- data/lib/massive_record/orm/schema/column_family.rb +3 -2
- data/lib/massive_record/orm/schema/{column_interface.rb → embedded_interface.rb} +38 -4
- data/lib/massive_record/orm/schema/field.rb +2 -0
- data/lib/massive_record/orm/schema/table_interface.rb +19 -2
- data/lib/massive_record/orm/single_table_inheritance.rb +37 -2
- data/lib/massive_record/orm/timestamps.rb +17 -7
- data/lib/massive_record/orm/validations.rb +4 -0
- data/lib/massive_record/orm/validations/associated.rb +50 -0
- data/lib/massive_record/rails/railtie.rb +31 -0
- data/lib/massive_record/version.rb +1 -1
- data/lib/massive_record/wrapper/cell.rb +8 -1
- data/massive_record.gemspec +4 -4
- data/spec/adapter/thrift/atomic_increment_spec.rb +16 -0
- data/spec/adapter/thrift/table_find_spec.rb +14 -2
- data/spec/adapter/thrift/table_spec.rb +6 -6
- data/spec/adapter/thrift/utf8_encoding_of_id_spec.rb +71 -0
- data/spec/orm/cases/attribute_methods_spec.rb +215 -22
- data/spec/orm/cases/auto_generate_id_spec.rb +1 -1
- data/spec/orm/cases/change_id_spec.rb +62 -0
- data/spec/orm/cases/default_id_spec.rb +25 -6
- data/spec/orm/cases/default_values_spec.rb +6 -3
- data/spec/orm/cases/dirty_spec.rb +150 -102
- data/spec/orm/cases/embedded_spec.rb +250 -0
- data/spec/orm/cases/{finder_default_scope.rb → finder_default_scope_spec.rb} +4 -0
- data/spec/orm/cases/finder_scope_spec.rb +96 -29
- data/spec/orm/cases/finders_spec.rb +57 -10
- data/spec/orm/cases/id_factory/atomic_incrementation_spec.rb +72 -0
- data/spec/orm/cases/id_factory/timestamp_spec.rb +61 -0
- data/spec/orm/cases/identity_map/identity_map_spec.rb +357 -0
- data/spec/orm/cases/identity_map/middleware_spec.rb +74 -0
- data/spec/orm/cases/log_subscriber_spec.rb +15 -2
- data/spec/orm/cases/observing_spec.rb +61 -0
- data/spec/orm/cases/persistence_spec.rb +151 -60
- data/spec/orm/cases/raw_data_spec.rb +58 -0
- data/spec/orm/cases/single_table_inheritance_spec.rb +58 -2
- data/spec/orm/cases/table_spec.rb +3 -3
- data/spec/orm/cases/time_zone_awareness_spec.rb +27 -0
- data/spec/orm/cases/timestamps_spec.rb +23 -109
- data/spec/orm/cases/validation_spec.rb +9 -0
- data/spec/orm/models/address.rb +5 -1
- data/spec/orm/models/address_with_timestamp.rb +12 -0
- data/spec/orm/models/car.rb +5 -0
- data/spec/orm/models/person.rb +13 -1
- data/spec/orm/models/person_with_timestamp.rb +4 -2
- data/spec/orm/models/test_class.rb +1 -0
- data/spec/orm/persistence/operations/atomic_operation_spec.rb +58 -0
- data/spec/orm/persistence/operations/destroy_spec.rb +22 -0
- data/spec/orm/persistence/operations/embedded/destroy_spec.rb +71 -0
- data/spec/orm/persistence/operations/embedded/insert_spec.rb +59 -0
- data/spec/orm/persistence/operations/embedded/operation_helpers_spec.rb +92 -0
- data/spec/orm/persistence/operations/embedded/reload_spec.rb +67 -0
- data/spec/orm/persistence/operations/embedded/update_spec.rb +60 -0
- data/spec/orm/persistence/operations/insert_spec.rb +31 -0
- data/spec/orm/persistence/operations/reload_spec.rb +48 -0
- data/spec/orm/persistence/operations/suppress_spec.rb +17 -0
- data/spec/orm/persistence/operations/table_operation_helpers_spec.rb +98 -0
- data/spec/orm/persistence/operations/update_spec.rb +25 -0
- data/spec/orm/persistence/operations_spec.rb +58 -0
- data/spec/orm/relations/interface_spec.rb +188 -0
- data/spec/orm/relations/metadata_spec.rb +92 -15
- data/spec/orm/relations/proxy/embedded_in_polymorphic_spec.rb +37 -0
- data/spec/orm/relations/proxy/embedded_in_spec.rb +66 -0
- data/spec/orm/relations/proxy/embeds_many_spec.rb +651 -0
- data/spec/orm/relations/proxy/references_many_spec.rb +466 -2
- data/spec/orm/schema/column_family_spec.rb +21 -0
- data/spec/orm/schema/embedded_interface_spec.rb +181 -0
- data/spec/orm/schema/field_spec.rb +7 -0
- data/spec/orm/schema/table_interface_spec.rb +31 -1
- data/spec/shared/orm/id_factories.rb +44 -0
- data/spec/shared/orm/model_with_timestamps.rb +132 -0
- data/spec/shared/orm/persistence/a_persistence_embedded_operation_class.rb +3 -0
- data/spec/shared/orm/persistence/a_persistence_operation_class.rb +11 -0
- data/spec/shared/orm/persistence/a_persistence_table_operation_class.rb +11 -0
- data/spec/shared/orm/relations/proxy.rb +9 -2
- data/spec/spec_helper.rb +9 -0
- data/spec/support/mock_massive_record_connection.rb +2 -1
- metadata +106 -21
- data/spec/orm/cases/column_spec.rb +0 -49
- data/spec/orm/cases/id_factory_spec.rb +0 -92
- data/spec/orm/schema/column_interface_spec.rb +0 -136
@@ -42,6 +42,24 @@ module MassiveRecord
|
|
42
42
|
debug " " + [name, description, options].compact.join(" ")
|
43
43
|
end
|
44
44
|
|
45
|
+
def identity_map(event)
|
46
|
+
return unless logger.debug?
|
47
|
+
|
48
|
+
payload = event.payload
|
49
|
+
name = '%s (%.1fms)' % [payload[:name], event.duration]
|
50
|
+
records = payload[:records]
|
51
|
+
ids = "id(s): #{records.collect(&:id)}"
|
52
|
+
|
53
|
+
if odd?
|
54
|
+
name = color(name, CYAN, true)
|
55
|
+
description = color(description, nil, true)
|
56
|
+
else
|
57
|
+
name = color(name, MAGENTA, true)
|
58
|
+
end
|
59
|
+
|
60
|
+
debug " " + [name, ids].compact.join(" ")
|
61
|
+
end
|
62
|
+
|
45
63
|
def query(event)
|
46
64
|
return unless logger.debug?
|
47
65
|
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module MassiveRecord
|
2
|
+
module ORM
|
3
|
+
|
4
|
+
#
|
5
|
+
# MassiveRecord Observer. Greatly influenced by ActiveRecord's
|
6
|
+
# way of doing callbacks, thus should feel familiar to most people.
|
7
|
+
#
|
8
|
+
# NOTE that if you are using rails you should add observers to into
|
9
|
+
# your application.rb configuration file like this:
|
10
|
+
#
|
11
|
+
# config.massive_record.observers = :person_observer, :audit_observer
|
12
|
+
#
|
13
|
+
# This will ensure that observers are loaded correctly. If you are not
|
14
|
+
# using rails you can do: MassiveRecord::ORM::Base.instantiate_observers
|
15
|
+
# after your application has been initialized.
|
16
|
+
#
|
17
|
+
# Example of usage:
|
18
|
+
#
|
19
|
+
# class Person < MassiveRecord::ORM::Table
|
20
|
+
# column_family :info do
|
21
|
+
# field :name
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# class PersonObserver < MassiveRecord::ORM::Observer
|
26
|
+
# def after_save(saved_person_record)
|
27
|
+
# # do something here after people are being saved
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# class AuditObserver < MassiveRecord::ORM::Observer
|
32
|
+
# observe :person, :and, :other, :classes
|
33
|
+
#
|
34
|
+
# def after_save(saved_person_record)
|
35
|
+
# # do something here after people are being saved
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
class Observer < ActiveModel::Observer
|
40
|
+
protected
|
41
|
+
|
42
|
+
def observed_classes
|
43
|
+
klasses = super
|
44
|
+
klasses + klasses.map { |klass| klass.descendants }.flatten
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_observer!(klass)
|
48
|
+
super
|
49
|
+
define_callbacks klass
|
50
|
+
end
|
51
|
+
|
52
|
+
def define_callbacks(klass)
|
53
|
+
observer = self
|
54
|
+
observer_name = observer.class.name.underscore.gsub('/', '__')
|
55
|
+
|
56
|
+
MassiveRecord::ORM::Callbacks::CALLBACKS.each do |callback|
|
57
|
+
next unless respond_to?(callback)
|
58
|
+
callback_meth = :"_notify_#{observer_name}_for_#{callback}"
|
59
|
+
unless klass.respond_to?(callback_meth)
|
60
|
+
klass.send(:define_method, callback_meth) do |&block|
|
61
|
+
observer.send(callback, self, &block)
|
62
|
+
end
|
63
|
+
klass.send(callback, callback_meth)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'massive_record/orm/persistence/operations'
|
2
|
+
|
1
3
|
module MassiveRecord
|
2
4
|
module ORM
|
3
5
|
module Persistence
|
@@ -14,42 +16,6 @@ module MassiveRecord
|
|
14
16
|
def destroy_all
|
15
17
|
all.each { |record| record.destroy }
|
16
18
|
end
|
17
|
-
|
18
|
-
|
19
|
-
#
|
20
|
-
# Iterates over tables and column families and ensure that we
|
21
|
-
# have what we need
|
22
|
-
#
|
23
|
-
def ensure_that_we_have_table_and_column_families! # :nodoc:
|
24
|
-
#
|
25
|
-
# TODO: Can we skip checking if it exists at all, and instead, rescue it if it does not?
|
26
|
-
#
|
27
|
-
hbase_create_table! unless table.exists?
|
28
|
-
raise ColumnFamiliesMissingError.new(self, calculate_missing_family_names) if calculate_missing_family_names.any?
|
29
|
-
end
|
30
|
-
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
#
|
35
|
-
# Creates table for this ORM class
|
36
|
-
#
|
37
|
-
def hbase_create_table!
|
38
|
-
missing_family_names = calculate_missing_family_names
|
39
|
-
table.create_column_families(missing_family_names) unless missing_family_names.empty?
|
40
|
-
table.save
|
41
|
-
end
|
42
|
-
|
43
|
-
#
|
44
|
-
# Calculate which column families are missing in the database in
|
45
|
-
# context of what the schema instructs.
|
46
|
-
#
|
47
|
-
def calculate_missing_family_names
|
48
|
-
existing_family_names = table.fetch_column_families.collect(&:name) rescue []
|
49
|
-
expected_family_names = column_families ? column_families.collect(&:name) : []
|
50
|
-
|
51
|
-
expected_family_names.collect(&:to_s) - existing_family_names.collect(&:to_s)
|
52
|
-
end
|
53
19
|
end
|
54
20
|
|
55
21
|
|
@@ -67,7 +33,7 @@ module MassiveRecord
|
|
67
33
|
|
68
34
|
|
69
35
|
def reload
|
70
|
-
|
36
|
+
Operations.reload(self).execute
|
71
37
|
self
|
72
38
|
end
|
73
39
|
|
@@ -101,11 +67,31 @@ module MassiveRecord
|
|
101
67
|
end
|
102
68
|
|
103
69
|
def destroy
|
104
|
-
@destroyed = (persisted? ?
|
70
|
+
@destroyed = (persisted? ? do_destroy : true) and freeze
|
105
71
|
end
|
106
72
|
alias_method :delete, :destroy
|
107
73
|
|
108
74
|
|
75
|
+
def change_id!(new_id)
|
76
|
+
old_id, self.id = id, new_id
|
77
|
+
|
78
|
+
@new_record = true
|
79
|
+
unless save
|
80
|
+
raise <<-TXT
|
81
|
+
Unable to save #{self.class} with updated id '#{new_id}'.
|
82
|
+
Old id '#{old_id}' was not deleted so in theory nothing should be changed in the database.
|
83
|
+
TXT
|
84
|
+
end
|
85
|
+
|
86
|
+
unless self.class.find(old_id).destroy
|
87
|
+
raise <<-TXT
|
88
|
+
Unable to destroy #{self.class} with id '#{old_id}'.
|
89
|
+
You now how duplicate records in the database. New id is: '#{new_id}.'
|
90
|
+
TXT
|
91
|
+
end
|
92
|
+
|
93
|
+
reload
|
94
|
+
end
|
109
95
|
|
110
96
|
|
111
97
|
def increment(attr_name, by = 1)
|
@@ -119,21 +105,11 @@ module MassiveRecord
|
|
119
105
|
increment(attr_name, by).update_attribute(attr_name, self[attr_name])
|
120
106
|
end
|
121
107
|
|
122
|
-
# Atomic increment of an attribute. Please note that it's the
|
123
|
-
# adapter (or the wrapper) which needs to guarantee that the update
|
124
|
-
# is atomic, and as of writing this the Thrift adapter / wrapper does
|
125
|
-
# not do this anatomic.
|
126
108
|
def atomic_increment!(attr_name, by = 1)
|
127
|
-
|
128
|
-
attr_name = attr_name.to_s
|
129
|
-
|
130
|
-
ensure_proper_binary_integer_representation(attr_name)
|
131
|
-
|
132
|
-
self[attr_name] = row_for_record.atomic_increment(attributes_schema[attr_name].unique_name, by)
|
133
|
-
@new_record = false
|
134
|
-
self[attr_name]
|
109
|
+
atomic_operation(:increment, attr_name, by)
|
135
110
|
end
|
136
111
|
|
112
|
+
|
137
113
|
def decrement(attr_name, by = 1)
|
138
114
|
raise NotNumericalFieldError unless attributes_schema[attr_name.to_s].type == :integer
|
139
115
|
self[attr_name] ||= 0
|
@@ -144,6 +120,11 @@ module MassiveRecord
|
|
144
120
|
def decrement!(attr_name, by = 1)
|
145
121
|
decrement(attr_name, by).update_attribute(attr_name, self[attr_name])
|
146
122
|
end
|
123
|
+
|
124
|
+
def atomic_decrement!(attr_name, by = 1)
|
125
|
+
atomic_operation(:decrement, attr_name, by)
|
126
|
+
end
|
127
|
+
|
147
128
|
|
148
129
|
|
149
130
|
private
|
@@ -151,94 +132,41 @@ module MassiveRecord
|
|
151
132
|
|
152
133
|
def create_or_update
|
153
134
|
raise ReadOnlyRecord if readonly?
|
135
|
+
|
154
136
|
!!(new_record? ? create : update)
|
155
137
|
end
|
156
138
|
|
157
139
|
def create
|
158
|
-
self.
|
159
|
-
|
160
|
-
raise RecordNotUnique if check_record_uniqueness_on_create && self.class.exists?(id)
|
161
|
-
|
162
|
-
if saved = store_record_to_database('create')
|
163
|
-
@new_record = false
|
140
|
+
Operations.insert(self).execute.tap do |saved|
|
141
|
+
@new_record = false if saved
|
164
142
|
end
|
165
|
-
saved
|
166
143
|
end
|
167
144
|
|
168
|
-
def update(attribute_names_to_update =
|
169
|
-
self.
|
170
|
-
|
171
|
-
store_record_to_database('update', attribute_names_to_update)
|
145
|
+
def update(attribute_names_to_update = attributes_with_embedded)
|
146
|
+
Operations.update(self, :attribute_names_to_update => attribute_names_to_update).execute
|
172
147
|
end
|
173
148
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
#
|
178
|
-
# Takes care of the actual storing of the record to the database
|
179
|
-
# Both update and create is using this
|
180
|
-
#
|
181
|
-
def store_record_to_database(action, attribute_names_to_update = [])
|
182
|
-
row = row_for_record
|
183
|
-
row.values = attributes_to_row_values_hash(attribute_names_to_update)
|
184
|
-
row.save
|
149
|
+
def do_destroy
|
150
|
+
Operations.destroy(self).execute
|
185
151
|
end
|
186
152
|
|
187
|
-
|
188
|
-
|
189
153
|
#
|
190
|
-
#
|
191
|
-
#
|
154
|
+
# Atomic decrement of an attribute. Please note that it's the
|
155
|
+
# adapter (or the wrapper) which needs to guarantee that the update
|
156
|
+
# is atomic. Thrift adapter is working with atomic decrementation.
|
192
157
|
#
|
193
|
-
def
|
194
|
-
|
195
|
-
|
196
|
-
MassiveRecord::Wrapper::Row.new({
|
197
|
-
:id => id,
|
198
|
-
:table => self.class.table
|
199
|
-
})
|
158
|
+
def atomic_operation(operation, attr_name, by)
|
159
|
+
Operations.atomic_operation(self, :operation => operation, :attr_name => attr_name, :by => by).execute
|
200
160
|
end
|
201
161
|
|
202
|
-
#
|
203
|
-
# Returns attributes on a form which Wrapper::Row expects
|
204
|
-
#
|
205
|
-
def attributes_to_row_values_hash(only_attr_names = [])
|
206
|
-
values = Hash.new { |hash, key| hash[key] = Hash.new }
|
207
|
-
|
208
|
-
attributes_schema.each do |attr_name, orm_field|
|
209
|
-
next unless only_attr_names.empty? || only_attr_names.include?(attr_name)
|
210
|
-
values[orm_field.column_family.name][orm_field.column] = orm_field.encode(self[attr_name])
|
211
|
-
end
|
212
162
|
|
213
|
-
values
|
214
|
-
end
|
215
163
|
|
216
164
|
#
|
217
|
-
#
|
218
|
-
#
|
219
|
-
# has a string representation of a number, like "1", instead of
|
220
|
-
# the binary representation, like "\x00\x00\x00\x00\x00\x00\x00\x01".
|
165
|
+
# Gives you all attribute names pluss all known embedded
|
166
|
+
# attributes names. Is used if dirty is active.
|
221
167
|
#
|
222
|
-
|
223
|
-
|
224
|
-
# database and have never used MassiveRecord before we should not
|
225
|
-
# need to do this at all; numbers are now stored as hex, but for
|
226
|
-
# backward compatibility we are doing this.
|
227
|
-
#
|
228
|
-
# Now, there is a risk of doing this; if two calls are made to
|
229
|
-
# atomic_increment! on a record where it's value is a string
|
230
|
-
# representation this operation might be compromised. Therefor
|
231
|
-
# you need to enable this feature.
|
232
|
-
#
|
233
|
-
def ensure_proper_binary_integer_representation(attr_name)
|
234
|
-
return if !backward_compatibility_integers_might_be_persisted_as_strings || new_record?
|
235
|
-
|
236
|
-
field = attributes_schema[attr_name]
|
237
|
-
raise "Not an integer field" unless field.try(:type) == :integer
|
238
|
-
|
239
|
-
if raw_value = self.class.table.get(id, field.column_family.name, field.name)
|
240
|
-
store_record_to_database('update', [attr_name]) if raw_value =~ /\A\d*\Z/
|
241
|
-
end
|
168
|
+
def attributes_with_embedded
|
169
|
+
attributes.keys | relation_proxies_for_embedded.collect { |proxy| proxy.metadata.name }
|
242
170
|
end
|
243
171
|
end
|
244
172
|
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'massive_record/orm/persistence/operations/suppress'
|
2
|
+
|
3
|
+
require 'massive_record/orm/persistence/operations/insert'
|
4
|
+
require 'massive_record/orm/persistence/operations/update'
|
5
|
+
require 'massive_record/orm/persistence/operations/destroy'
|
6
|
+
require 'massive_record/orm/persistence/operations/reload'
|
7
|
+
require 'massive_record/orm/persistence/operations/atomic_operation'
|
8
|
+
|
9
|
+
require 'massive_record/orm/persistence/operations/embedded/insert'
|
10
|
+
require 'massive_record/orm/persistence/operations/embedded/update'
|
11
|
+
require 'massive_record/orm/persistence/operations/embedded/destroy'
|
12
|
+
require 'massive_record/orm/persistence/operations/embedded/reload'
|
13
|
+
|
14
|
+
module MassiveRecord
|
15
|
+
module ORM
|
16
|
+
module Persistence
|
17
|
+
|
18
|
+
#
|
19
|
+
# The persistence Operations are in charge of inserting,
|
20
|
+
# updating and destroying records.
|
21
|
+
#
|
22
|
+
# It's reason for even existing is that we need to
|
23
|
+
# do these operations differently based on if we are
|
24
|
+
# saving a record which has a Table as it's class or saving
|
25
|
+
# an embedded record.
|
26
|
+
#
|
27
|
+
# The Persistence module will call upon
|
28
|
+
# Operations.insert(record, options) and execute on the returned
|
29
|
+
# object. Based on what kind of record we are getting in we
|
30
|
+
# can determine what kind of Operation object to return
|
31
|
+
#
|
32
|
+
module Operations
|
33
|
+
class << self
|
34
|
+
def suppress
|
35
|
+
@suppressed = true
|
36
|
+
yield
|
37
|
+
ensure
|
38
|
+
@suppressed = false
|
39
|
+
end
|
40
|
+
|
41
|
+
def suppressed?
|
42
|
+
!!@suppressed
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def insert(record, options = {})
|
47
|
+
operator_for :insert, record, options
|
48
|
+
end
|
49
|
+
|
50
|
+
def update(record, options = {})
|
51
|
+
operator_for :update, record, options
|
52
|
+
end
|
53
|
+
|
54
|
+
def destroy(record, options = {})
|
55
|
+
operator_for :destroy, record, options
|
56
|
+
end
|
57
|
+
|
58
|
+
def atomic_operation(record, options = {})
|
59
|
+
operator_for :atomic_operation, record, options
|
60
|
+
end
|
61
|
+
|
62
|
+
def reload(record, options = {})
|
63
|
+
operator_for :reload, record, options
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def operator_for(operation, record, options)
|
69
|
+
if suppressed?
|
70
|
+
klass = Suppress
|
71
|
+
else
|
72
|
+
class_parts = [self]
|
73
|
+
class_parts << "Embedded" if record.kind_of? ORM::Embedded
|
74
|
+
class_parts << operation.to_s.classify
|
75
|
+
|
76
|
+
klass = class_parts.join("::").constantize
|
77
|
+
end
|
78
|
+
|
79
|
+
klass.new(record, options)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
attr_reader :record, :klass, :options
|
85
|
+
|
86
|
+
|
87
|
+
def initialize(record, options = {})
|
88
|
+
@record = record
|
89
|
+
@klass = record.class
|
90
|
+
@options = options
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
def execute
|
95
|
+
raise "Not implemented"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'massive_record/orm/persistence/operations/table_operation_helpers'
|
2
|
+
|
3
|
+
module MassiveRecord
|
4
|
+
module ORM
|
5
|
+
module Persistence
|
6
|
+
module Operations
|
7
|
+
class AtomicOperation
|
8
|
+
include Operations, TableOperationHelpers
|
9
|
+
|
10
|
+
def execute
|
11
|
+
raise NotNumericalFieldError unless record.attributes_schema[attr_name].type == :integer
|
12
|
+
|
13
|
+
ensure_that_we_have_table_and_column_families!
|
14
|
+
ensure_proper_binary_integer_representation(attr_name)
|
15
|
+
record[attr_name] = row_for_record.send("atomic_#{operation}", record.attributes_schema[attr_name].unique_name, by)
|
16
|
+
ensure
|
17
|
+
record.instance_variable_set(:@new_record, false)
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
#
|
24
|
+
# To cope with the problem which arises when you ask to
|
25
|
+
# do atomic incrementation / decrementation of an attribute and that attribute
|
26
|
+
# has a string representation of a number, like "1", instead of
|
27
|
+
# the binary representation, like "\x00\x00\x00\x00\x00\x00\x00\x01".
|
28
|
+
#
|
29
|
+
# We then need to re-write that string representation into
|
30
|
+
# hex representation. Now, if you are on a completely new
|
31
|
+
# database and have never used MassiveRecord before we should not
|
32
|
+
# need to do this at all; numbers are now stored as hex, but for
|
33
|
+
# backward compatibility we are doing this.
|
34
|
+
#
|
35
|
+
# Now, there is a risk of doing this; if two calls are made to
|
36
|
+
# atomic_increment! or atomic_decrement! on a record where it's value is a string
|
37
|
+
# representation this operation might be compromised. Therefor
|
38
|
+
# you need to enable this feature.
|
39
|
+
#
|
40
|
+
def ensure_proper_binary_integer_representation(attr_name)
|
41
|
+
return if !klass.backward_compatibility_integers_might_be_persisted_as_strings || record.new_record?
|
42
|
+
|
43
|
+
field = record.attributes_schema[attr_name]
|
44
|
+
|
45
|
+
if raw_value = klass.table.get(record.id, field.column_family.name, field.name)
|
46
|
+
store_record_to_database('update', [attr_name]) if raw_value =~ /\A\d*\Z/
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def operation
|
52
|
+
options[:operation] or raise "Missing option :operation"
|
53
|
+
end
|
54
|
+
|
55
|
+
def attr_name
|
56
|
+
@attr_name ||= if options[:attr_name].present?
|
57
|
+
options[:attr_name].to_s
|
58
|
+
else
|
59
|
+
raise "Missing option :attr_name"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def by
|
64
|
+
options[:by] or raise "Missing option :by"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|