ripple 1.0.0.beta → 1.0.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +10 -6
- data/Gemfile +6 -15
- data/Gemfile.rails30 +3 -0
- data/Gemfile.rails31 +3 -0
- data/Gemfile.rails32 +3 -0
- data/Guardfile +3 -1
- data/LICENSE +16 -0
- data/README.markdown +173 -0
- data/RELEASE_NOTES.textile +286 -0
- data/Rakefile +19 -0
- data/lib/rails/generators/ripple/configuration/templates/ripple.yml +1 -0
- data/lib/rails/generators/ripple/model/model_generator.rb +1 -1
- data/lib/rails/generators/ripple/model/templates/{model.rb → model.rb.erb} +0 -0
- data/lib/rails/generators/ripple/observer/observer_generator.rb +1 -1
- data/lib/rails/generators/ripple/observer/templates/{observer.rb → observer.rb.erb} +0 -2
- data/lib/rails/generators/ripple/test/templates/cucumber.rb.erb +7 -0
- data/lib/rails/generators/ripple/test/test_generator.rb +17 -13
- data/lib/rails/generators/ripple_generator.rb +1 -0
- data/lib/ripple/associations.rb +65 -55
- data/lib/ripple/associations/embedded.rb +1 -1
- data/lib/ripple/associations/linked.rb +1 -1
- data/lib/ripple/associations/many.rb +1 -1
- data/lib/ripple/associations/many_embedded_proxy.rb +3 -2
- data/lib/ripple/associations/many_linked_proxy.rb +1 -1
- data/lib/ripple/associations/many_reference_proxy.rb +7 -5
- data/lib/ripple/associations/proxy.rb +2 -2
- data/lib/ripple/attribute_methods.rb +69 -61
- data/lib/ripple/attribute_methods/dirty.rb +2 -2
- data/lib/ripple/attribute_methods/read.rb +4 -2
- data/lib/ripple/callbacks.rb +23 -26
- data/lib/ripple/conflict/basic_resolver.rb +6 -2
- data/lib/ripple/conflict/document_hooks.rb +26 -0
- data/lib/ripple/conflict/resolver.rb +10 -2
- data/lib/ripple/conflict/test_helper.rb +3 -2
- data/lib/ripple/conversion.rb +1 -0
- data/lib/ripple/core_ext.rb +1 -0
- data/lib/ripple/core_ext/casting.rb +2 -0
- data/lib/ripple/core_ext/indexes.rb +89 -0
- data/lib/ripple/document.rb +23 -22
- data/lib/ripple/document/key.rb +12 -14
- data/lib/ripple/document/persistence.rb +99 -84
- data/lib/ripple/embedded_document.rb +9 -10
- data/lib/ripple/embedded_document/persistence.rb +42 -44
- data/lib/ripple/i18n.rb +4 -1
- data/lib/ripple/indexes.rb +151 -0
- data/lib/ripple/locale/en.yml +4 -0
- data/lib/ripple/locale/fr.yml +24 -0
- data/lib/ripple/nested_attributes.rb +92 -90
- data/lib/ripple/properties.rb +2 -1
- data/lib/ripple/railtie.rb +9 -0
- data/lib/ripple/railties/ripple.rake +32 -15
- data/lib/ripple/serialization.rb +50 -52
- data/lib/ripple/test_server.rb +1 -2
- data/lib/ripple/timestamps.rb +6 -8
- data/lib/ripple/validations.rb +19 -21
- data/lib/ripple/version.rb +1 -1
- data/ripple.gemspec +6 -5
- data/spec/generators/ripple/configuration_generator_spec.rb +9 -0
- data/spec/generators/ripple/js_generator_spec.rb +14 -0
- data/spec/generators/ripple/model_generator_spec.rb +64 -0
- data/spec/generators/ripple/observer_generator_spec.rb +20 -0
- data/spec/generators/ripple/test_generator_spec.rb +116 -0
- data/spec/generators/ripple_generator_spec.rb +11 -0
- data/spec/integration/ripple/conflict_resolution_spec.rb +35 -4
- data/spec/integration/ripple/indexes_spec.rb +47 -0
- data/spec/ripple/associations/many_embedded_proxy_spec.rb +50 -60
- data/spec/ripple/associations/many_linked_proxy_spec.rb +2 -2
- data/spec/ripple/associations/many_reference_proxy_spec.rb +1 -1
- data/spec/ripple/associations_spec.rb +16 -7
- data/spec/ripple/attribute_methods_spec.rb +43 -2
- data/spec/ripple/callbacks_spec.rb +120 -101
- data/spec/ripple/conversion_spec.rb +5 -13
- data/spec/ripple/core_ext_spec.rb +93 -15
- data/spec/ripple/finders_spec.rb +0 -2
- data/spec/ripple/indexes_spec.rb +111 -0
- data/spec/ripple/observable_spec.rb +1 -2
- data/spec/ripple/persistence_spec.rb +55 -32
- data/spec/ripple/properties_spec.rb +1 -1
- data/spec/ripple/ripple_spec.rb +5 -5
- data/spec/ripple/timestamps_spec.rb +9 -2
- data/spec/ripple/validations_spec.rb +50 -52
- data/spec/spec_helper.rb +9 -2
- data/spec/support/generator_setup.rb +26 -0
- data/spec/support/models.rb +1 -0
- data/spec/support/models/box.rb +1 -0
- data/spec/support/models/clock.rb +1 -1
- data/spec/support/models/indexer.rb +26 -0
- data/spec/support/models/post.rb +3 -2
- data/spec/support/models/widget.rb +2 -0
- data/spec/support/search.rb +2 -2
- data/spec/support/test_server.rb +23 -11
- data/spec/support/test_server.yml.example +1 -1
- metadata +159 -135
- data/spec/support/mocks.rb +0 -4
data/lib/ripple/callbacks.rb
CHANGED
@@ -38,36 +38,33 @@ module Ripple
|
|
38
38
|
end
|
39
39
|
|
40
40
|
# @private
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
run_save_callbacks do
|
45
|
-
super
|
46
|
-
end
|
41
|
+
def really_save(*args, &block)
|
42
|
+
run_save_callbacks do
|
43
|
+
super
|
47
44
|
end
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
45
|
+
end
|
46
|
+
|
47
|
+
def run_save_callbacks
|
48
|
+
state = new? ? :create : :update
|
49
|
+
run_callbacks(:save) do
|
50
|
+
run_callbacks(state) do
|
51
|
+
yield
|
55
52
|
end
|
56
53
|
end
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
54
|
+
end
|
55
|
+
|
56
|
+
# @private
|
57
|
+
def destroy!(*args, &block)
|
58
|
+
run_callbacks(:destroy) do
|
59
|
+
super
|
63
60
|
end
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
61
|
+
end
|
62
|
+
|
63
|
+
# @private
|
64
|
+
def valid?(*args, &block)
|
65
|
+
@_on_validate = new? ? :create : :update
|
66
|
+
run_callbacks(:validation) do
|
67
|
+
super
|
71
68
|
end
|
72
69
|
end
|
73
70
|
end
|
@@ -28,6 +28,10 @@ module Ripple
|
|
28
28
|
|
29
29
|
private
|
30
30
|
|
31
|
+
def undeleted_siblings
|
32
|
+
@undeleted_siblings ||= siblings.reject(&:deleted?)
|
33
|
+
end
|
34
|
+
|
31
35
|
def process_properties
|
32
36
|
model_class.properties.each do |name, property|
|
33
37
|
document.send(:"#{name}=", resolved_property_value_for(property))
|
@@ -53,7 +57,7 @@ module Ripple
|
|
53
57
|
end
|
54
58
|
|
55
59
|
def resolved_property_value_for(property)
|
56
|
-
uniq_values =
|
60
|
+
uniq_values = undeleted_siblings.map(&property.key).uniq
|
57
61
|
|
58
62
|
value = if uniq_values.size == 1
|
59
63
|
uniq_values.first
|
@@ -69,7 +73,7 @@ module Ripple
|
|
69
73
|
|
70
74
|
def resolved_association_value_for(association, proxy_value_method)
|
71
75
|
# the association proxy doesn't uniquify well, so we have to use the target or links directly
|
72
|
-
uniq_values =
|
76
|
+
uniq_values = undeleted_siblings.map { |s| s.send(association.name).__send__(proxy_value_method) }.uniq
|
73
77
|
|
74
78
|
return uniq_values.first if uniq_values.size == 1
|
75
79
|
remaining_conflicts << association.name
|
@@ -4,13 +4,39 @@ module Ripple
|
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
6
|
module ClassMethods
|
7
|
+
# @return [Proc] the registered conflict handler
|
7
8
|
attr_reader :on_conflict_block
|
8
9
|
|
10
|
+
# Registers a conflict handler for this model.
|
11
|
+
#
|
12
|
+
# @param [Array<Symbol>] expected_conflicts the list of properties and associations
|
13
|
+
# you expect to be in conflict.
|
14
|
+
# @yield the conflict handler block
|
15
|
+
# @yieldparam [Array<Ripple::Document>] siblings the sibling documents
|
16
|
+
# @yieldparam [Array<Symbol>] conflicts the properties and associations that could not
|
17
|
+
# be resolved by ripple's basic resolution logic.
|
18
|
+
#
|
19
|
+
# The block is instance_eval'd in the context of a partially resolved model instance.
|
20
|
+
# Thus, you should apply your resolution logic directly to self. Before calling
|
21
|
+
# your block, Ripple attempts some basic resolution on your behalf:
|
22
|
+
#
|
23
|
+
# * Any property or association for which all siblings agree will be set to the common value.
|
24
|
+
# * created_at will be set to the minimum value.
|
25
|
+
# * updated_at will be set to the maximum value.
|
26
|
+
# * All other properties and associations will be set to the default: nil or the default
|
27
|
+
# value for a property, nil for a one association, and an empty array for a many association.
|
28
|
+
#
|
29
|
+
# Note that any conflict you do not resolve is a potential source of data loss (since ripple
|
30
|
+
# sets it to a default such as nil). It is recommended (but not required) that you pass the list
|
31
|
+
# of expected conflicts, as that informs ripple of what conflicts your block handles. If it detects
|
32
|
+
# conflicts for any other properties or associations, a NotImplementedError will be raised.
|
9
33
|
def on_conflict(*expected_conflicts, &block)
|
10
34
|
@expected_conflicts = expected_conflicts
|
11
35
|
@on_conflict_block = block
|
12
36
|
end
|
13
37
|
|
38
|
+
# @return [Array<Symbol>] list of properties and associations that are expected
|
39
|
+
# to be in conflict.
|
14
40
|
def expected_conflicts
|
15
41
|
@expected_conflicts ||= []
|
16
42
|
end
|
@@ -11,7 +11,7 @@ module Ripple
|
|
11
11
|
|
12
12
|
def self.to_proc
|
13
13
|
@to_proc ||= lambda do |robject|
|
14
|
-
possible_model_classes = robject.siblings.map { |s| s.data['_type'] }.uniq
|
14
|
+
possible_model_classes = robject.siblings.map { |s| s.data && s.data['_type'] }.compact.uniq
|
15
15
|
return nil unless possible_model_classes.size == 1
|
16
16
|
|
17
17
|
resolver = new(robject, possible_model_classes.first.constantize)
|
@@ -36,7 +36,15 @@ module Ripple
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def siblings
|
39
|
-
@siblings ||= @robject.siblings.map
|
39
|
+
@siblings ||= @robject.siblings.map do |s|
|
40
|
+
@model_class.send(:instantiate, s).tap do |record|
|
41
|
+
# TODO: make the deleted conditional explicit by putting logic in
|
42
|
+
# RObject to know it has loaded a deleted sibling.
|
43
|
+
# Here we assume it is deleted if the data is nil because
|
44
|
+
# that's the only way we know of that the data can be nil.
|
45
|
+
record.instance_variable_set(:@deleted, true) if s.data.nil?
|
46
|
+
end
|
47
|
+
end
|
40
48
|
end
|
41
49
|
|
42
50
|
def document
|
@@ -9,14 +9,15 @@ module Ripple
|
|
9
9
|
|
10
10
|
begin
|
11
11
|
klass, key = main_record.class, main_record.key
|
12
|
+
raise "#{klass.bucket.name} allow_mult property is false!" unless klass.bucket.allow_mult
|
12
13
|
records = modifiers.map { |_| klass.find!(key) }
|
13
14
|
|
14
15
|
records.zip(modifiers).each do |(record, modifier)|
|
15
|
-
# necessary to get conflict, so riak thinks they are being saved by different clients
|
16
|
+
# necessary to get conflict on 0.14 and earlier, so riak thinks they are being saved by different clients
|
16
17
|
Ripple.client.client_id += 1
|
17
18
|
|
18
19
|
modifier.call(record)
|
19
|
-
record.save!
|
20
|
+
record.save! unless record.deleted?
|
20
21
|
end
|
21
22
|
|
22
23
|
robject = klass.bucket.get(key)
|
data/lib/ripple/conversion.rb
CHANGED
data/lib/ripple/core_ext.rb
CHANGED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'tzinfo'
|
2
|
+
require 'active_support/core_ext/date/conversions'
|
3
|
+
require 'active_support/core_ext/date/zones'
|
4
|
+
require 'active_support/core_ext/date_time/conversions'
|
5
|
+
require 'active_support/core_ext/date_time/zones'
|
6
|
+
require 'active_support/core_ext/time/conversions'
|
7
|
+
require 'active_support/core_ext/time/zones'
|
8
|
+
require 'active_support/core_ext/string/conversions'
|
9
|
+
require 'ripple/property_type_mismatch'
|
10
|
+
require 'set'
|
11
|
+
|
12
|
+
# @private
|
13
|
+
class Object
|
14
|
+
def to_ripple_index(type)
|
15
|
+
case type
|
16
|
+
when 'bin'
|
17
|
+
to_s
|
18
|
+
when 'int'
|
19
|
+
to_i
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# @private
|
25
|
+
class Time
|
26
|
+
def to_ripple_index(type)
|
27
|
+
case type
|
28
|
+
when 'bin'
|
29
|
+
utc.send(Ripple.date_format)
|
30
|
+
when 'int'
|
31
|
+
# Use millisecond-precision
|
32
|
+
(utc.to_f * 1000).round
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# @private
|
38
|
+
class Date
|
39
|
+
def to_ripple_index(type)
|
40
|
+
case type
|
41
|
+
when 'bin'
|
42
|
+
to_s(Ripple.date_format)
|
43
|
+
when 'int'
|
44
|
+
to_time(:utc).to_ripple_index(type)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# @private
|
50
|
+
class DateTime
|
51
|
+
def to_ripple_index(type)
|
52
|
+
case type
|
53
|
+
when 'bin'
|
54
|
+
utc.to_s(Ripple.date_format)
|
55
|
+
when 'int'
|
56
|
+
(utc.to_f * 1000).round
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# @private
|
62
|
+
module ActiveSupport
|
63
|
+
class TimeWithZone
|
64
|
+
def to_ripple_index(type)
|
65
|
+
utc.to_ripple_index(type)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# @private
|
71
|
+
module Enumerable
|
72
|
+
def to_ripple_index(type)
|
73
|
+
Set.new(map {|v| v.to_ripple_index(type) })
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
if String < Enumerable
|
78
|
+
# Fix for 1.8, in which String is Enumerable
|
79
|
+
class String
|
80
|
+
def to_ripple_index(type)
|
81
|
+
case type
|
82
|
+
when 'bin'
|
83
|
+
to_s
|
84
|
+
when 'int'
|
85
|
+
to_i
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/ripple/document.rb
CHANGED
@@ -8,6 +8,7 @@ require 'ripple/document/finders'
|
|
8
8
|
require 'ripple/document/link'
|
9
9
|
require 'ripple/properties'
|
10
10
|
require 'ripple/attribute_methods'
|
11
|
+
require 'ripple/indexes'
|
11
12
|
require 'ripple/timestamps'
|
12
13
|
require 'ripple/validations'
|
13
14
|
require 'ripple/associations'
|
@@ -55,6 +56,8 @@ module Ripple
|
|
55
56
|
include Ripple::Document::Finders
|
56
57
|
include Ripple::AttributeMethods
|
57
58
|
include Ripple::Timestamps
|
59
|
+
include Ripple::Indexes
|
60
|
+
include Ripple::Indexes::DocumentMethods
|
58
61
|
include Ripple::Validations
|
59
62
|
include Ripple::Associations
|
60
63
|
include Ripple::Callbacks
|
@@ -72,32 +75,30 @@ module Ripple
|
|
72
75
|
end
|
73
76
|
end
|
74
77
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
78
|
+
def _root_document
|
79
|
+
self
|
80
|
+
end
|
79
81
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
82
|
+
# Returns true if the +comparison_object+ is the same object, or is of the same type and has the same key.
|
83
|
+
def ==(comparison_object)
|
84
|
+
comparison_object.equal?(self) ||
|
85
|
+
(comparison_object.class < Document && (comparison_object.instance_of?(self.class) || comparison_object.class.bucket.name == self.class.bucket.name) &&
|
86
|
+
!new? && comparison_object.key == key && !comparison_object.new?)
|
87
|
+
end
|
86
88
|
|
87
|
-
|
88
|
-
|
89
|
+
def eql?(other)
|
90
|
+
return true if other.equal?(self)
|
89
91
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
92
|
+
(other.class.equal?(self.class)) &&
|
93
|
+
!other.new? && !new? &&
|
94
|
+
(other.key == key)
|
95
|
+
end
|
94
96
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
end
|
97
|
+
def hash
|
98
|
+
if new?
|
99
|
+
super # every new document should be treated as a different doc
|
100
|
+
else
|
101
|
+
[self.class, key].hash
|
101
102
|
end
|
102
103
|
end
|
103
104
|
end
|
data/lib/ripple/document/key.rb
CHANGED
@@ -17,20 +17,18 @@ module Ripple
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
:key
|
33
|
-
end
|
20
|
+
# Reads the key for this Document.
|
21
|
+
def key
|
22
|
+
@key
|
23
|
+
end
|
24
|
+
|
25
|
+
# Sets the key for this Document.
|
26
|
+
def key=(value)
|
27
|
+
@key = value.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def key_attr
|
31
|
+
:key
|
34
32
|
end
|
35
33
|
end
|
36
34
|
end
|
@@ -8,8 +8,8 @@ module Ripple
|
|
8
8
|
module ClassMethods
|
9
9
|
|
10
10
|
# Instantiates a new record, applies attributes from a block, and saves it
|
11
|
-
def create(
|
12
|
-
new(
|
11
|
+
def create(*args, &block)
|
12
|
+
new(*args, &block).tap {|s| s.save }
|
13
13
|
end
|
14
14
|
|
15
15
|
# Destroys all records one at a time.
|
@@ -26,90 +26,105 @@ module Ripple
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
#
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
29
|
+
# @private
|
30
|
+
def initialize
|
31
|
+
super
|
32
|
+
@new = true
|
33
|
+
@deleted = false
|
34
|
+
end
|
35
|
+
|
36
|
+
# Determines whether this document has been deleted or not.
|
37
|
+
def deleted?
|
38
|
+
@deleted
|
39
|
+
end
|
40
|
+
|
41
|
+
# Determines whether this is a new document.
|
42
|
+
def new?
|
43
|
+
@new || false
|
44
|
+
end
|
45
|
+
|
46
|
+
# Updates a single attribute and then saves the document
|
47
|
+
# NOTE: THIS SKIPS VALIDATIONS! Use with caution.
|
48
|
+
# @return [true,false] whether the document succeeded in saving
|
49
|
+
def update_attribute(attribute, value)
|
50
|
+
send("#{attribute}=", value)
|
51
|
+
save(:validate => false)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Writes new attributes and then saves the document
|
55
|
+
# @return [true,false] whether the document succeeded in saving
|
56
|
+
def update_attributes(attrs)
|
57
|
+
self.attributes = attrs
|
58
|
+
save
|
59
|
+
end
|
60
|
+
|
61
|
+
# Saves the document in Riak.
|
62
|
+
# @return [true,false] whether the document succeeded in saving
|
63
|
+
def save(*args)
|
64
|
+
really_save(*args)
|
65
|
+
end
|
66
|
+
|
67
|
+
def really_save(*args)
|
68
|
+
update_robject
|
69
|
+
robject.store(self.class.quorums.slice(:w,:dw))
|
70
|
+
self.key = robject.key
|
71
|
+
@new = false
|
72
|
+
true
|
73
|
+
end
|
74
|
+
|
75
|
+
# Reloads the document from Riak
|
76
|
+
# @return self
|
77
|
+
def reload
|
78
|
+
return self if new?
|
79
|
+
@robject = @robject.reload(:force => true)
|
80
|
+
self.__send__(:raw_attributes=, @robject.data.except("_type"))
|
81
|
+
reset_associations
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
# Deletes the document from Riak and freezes this instance
|
86
|
+
def destroy!
|
87
|
+
robject.delete(self.class.quorums.slice(:rw)) unless new?
|
88
|
+
@deleted = true
|
89
|
+
freeze
|
90
|
+
end
|
91
|
+
|
92
|
+
def destroy
|
93
|
+
destroy!
|
94
|
+
true
|
95
|
+
rescue Riak::FailedRequest
|
96
|
+
false
|
97
|
+
end
|
98
|
+
|
99
|
+
# Freeze the attributes hash instead of the record itself to avoid
|
100
|
+
# errors when calling methods on frozen records.
|
101
|
+
def freeze
|
102
|
+
@attributes.freeze
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns +true+ if the attributes hash has been frozen.
|
106
|
+
def frozen?
|
107
|
+
@attributes.frozen?
|
108
|
+
end
|
109
|
+
|
110
|
+
attr_writer :robject
|
111
|
+
|
112
|
+
def robject
|
113
|
+
@robject ||= Riak::RObject.new(self.class.bucket, key).tap do |obj|
|
114
|
+
obj.content_type = "application/json"
|
111
115
|
end
|
112
116
|
end
|
117
|
+
|
118
|
+
def update_robject
|
119
|
+
robject.key = key if robject.key != key
|
120
|
+
robject.content_type = 'application/json'
|
121
|
+
robject.data = attributes_for_persistence
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
def attributes_for_persistence
|
126
|
+
raw_attributes.merge("_type" => self.class.name)
|
127
|
+
end
|
113
128
|
end
|
114
129
|
end
|
115
130
|
end
|