risky 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.rspec +1 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +38 -0
- data/README.markdown +8 -4
- data/Rakefile.rb +31 -0
- data/lib/risky.rb +274 -265
- data/lib/risky/gzip.rb +28 -0
- data/lib/risky/indexes.rb +4 -4
- data/lib/risky/inflector.rb +337 -0
- data/lib/risky/list_keys.rb +58 -0
- data/lib/risky/paginated_collection.rb +11 -0
- data/lib/risky/secondary_indexes.rb +196 -0
- data/lib/risky/version.rb +1 -1
- data/risky.gemspec +22 -0
- data/spec/risky/cron_list_spec.rb +52 -0
- data/spec/risky/crud_spec.rb +69 -0
- data/spec/risky/enumerable_spec.rb +45 -0
- data/spec/risky/gzip_spec.rb +73 -0
- data/spec/risky/indexes_spec.rb +34 -0
- data/spec/risky/resolver_spec.rb +55 -0
- data/spec/risky/secondary_indexes_spec.rb +222 -0
- data/spec/risky/threads_spec.rb +57 -0
- data/spec/risky_spec.rb +100 -0
- data/spec/spec_helper.rb +40 -0
- metadata +87 -27
- data/lib/risky/all.rb +0 -4
- data/lib/risky/threadsafe.rb +0 -42
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 365f66d97a0adebc22c466d92764fd1a7f16bef2
|
4
|
+
data.tar.gz: e57eb61d6dcb1a58b4899f123388acab66c9b7da
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3686bd912cab327d870870ec35661259e6360aa6e1b3c87ac44ab256c2ca22b651edb768394a9236160344c557ae83c4555bf79b2efbfb8e38c6067c8056d042
|
7
|
+
data.tar.gz: 7f948333bb7d410c949d6ec40dd8a0875e440e67dedcc2274b256dffe5479b06a2342d68812f41b7cccffdb8eebf321fb8a79a426b0e597535a8af34c5e4020e
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
risky (1.1.0)
|
5
|
+
riak-client (~> 1.4.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
beefcake (0.3.7)
|
11
|
+
builder (3.2.2)
|
12
|
+
diff-lcs (1.2.4)
|
13
|
+
excon (0.25.3)
|
14
|
+
i18n (0.6.5)
|
15
|
+
innertube (1.0.2)
|
16
|
+
multi_json (1.7.9)
|
17
|
+
riak-client (1.4.0)
|
18
|
+
beefcake (~> 0.3.7)
|
19
|
+
builder (>= 2.1.2)
|
20
|
+
i18n (>= 0.4.0)
|
21
|
+
innertube (~> 1.0.2)
|
22
|
+
multi_json (~> 1.0)
|
23
|
+
rspec (2.14.1)
|
24
|
+
rspec-core (~> 2.14.0)
|
25
|
+
rspec-expectations (~> 2.14.0)
|
26
|
+
rspec-mocks (~> 2.14.0)
|
27
|
+
rspec-core (2.14.5)
|
28
|
+
rspec-expectations (2.14.2)
|
29
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
30
|
+
rspec-mocks (2.14.3)
|
31
|
+
|
32
|
+
PLATFORMS
|
33
|
+
ruby
|
34
|
+
|
35
|
+
DEPENDENCIES
|
36
|
+
excon
|
37
|
+
risky!
|
38
|
+
rspec
|
data/README.markdown
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
Risky
|
2
2
|
=====
|
3
3
|
|
4
|
-
(Hey guys, I'll be porting over tests from our internal suite in the next few evenings. --Kyle)
|
5
|
-
|
6
4
|
A simple, lightweight object layer for Riak.
|
7
5
|
|
8
6
|
$ gem install risky
|
@@ -14,11 +12,11 @@ A simple, lightweight object layer for Riak.
|
|
14
12
|
User.new('clu', 'fights' => 'for the users').save
|
15
13
|
User['clu']['fights'] #=> 'for the users'
|
16
14
|
|
17
|
-
Built on top of
|
15
|
+
Built on top of basho's excellent riak-client, Risky provides basic
|
18
16
|
infrastructure for designing models with attributes (including defaults and
|
19
17
|
casting to/from JSON), conflict resolution, validation, lifecycle callbacks,
|
20
18
|
link-walking, mapreduce, and more. Modules are available for timestamps,
|
21
|
-
chronologically ordered lists, and
|
19
|
+
chronologically ordered lists, and secondary indexes (2i).
|
22
20
|
|
23
21
|
Risky does not provide the rich API of Ripple, but it also does not require
|
24
22
|
activesupport. It strives to be understandable, minimal, and modular. Magic is
|
@@ -56,6 +54,12 @@ Show me the code!
|
|
56
54
|
links :followers
|
57
55
|
end
|
58
56
|
|
57
|
+
Contributors
|
58
|
+
------------
|
59
|
+
|
60
|
+
Dalibor Nasevic ([@dalibor](https://github.com/dalibor))
|
61
|
+
Marc Heiligers ([@marcheiligers](https://github.com/marcheiligers))
|
62
|
+
|
59
63
|
License
|
60
64
|
-------
|
61
65
|
|
data/Rakefile.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
namespace :test do
|
4
|
+
desc "Delete test buckets"
|
5
|
+
task :delete_buckets do
|
6
|
+
require 'riak'
|
7
|
+
Riak.disable_list_keys_warnings = true
|
8
|
+
client = Riak::Client.new(:host => '127.0.0.1', :protocol => 'pbc')
|
9
|
+
bucket_names = [
|
10
|
+
'risky_enum',
|
11
|
+
'risky_indexes',
|
12
|
+
'risky_crud',
|
13
|
+
'risky_items',
|
14
|
+
'risky_users',
|
15
|
+
'risky_mult',
|
16
|
+
'risky_concurrent',
|
17
|
+
'risky_cron_list',
|
18
|
+
'risky_albums',
|
19
|
+
'risky_artists',
|
20
|
+
'risky_labels',
|
21
|
+
'risky_cities',
|
22
|
+
'risky_indexes_by_unique',
|
23
|
+
'risky_indexes_by_value'
|
24
|
+
]
|
25
|
+
bucket_names.each do |bucket_name|
|
26
|
+
bucket = client.bucket(bucket_name)
|
27
|
+
puts "Deleting keys in #{bucket_name}"
|
28
|
+
bucket.keys.map { |k| bucket.delete(k) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/risky.rb
CHANGED
@@ -1,302 +1,279 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'riak'
|
3
|
+
require 'multi_json'
|
4
|
+
|
1
5
|
class Risky
|
2
|
-
|
3
|
-
|
4
|
-
|
6
|
+
autoload :Invalid, 'risky/invalid'
|
7
|
+
autoload :NotFound, 'risky/not_found'
|
8
|
+
autoload :ListKeys, 'risky/list_keys'
|
9
|
+
autoload :CronList, 'risky/cron_list'
|
10
|
+
autoload :Indexes, 'risky/indexes'
|
11
|
+
autoload :SecondaryIndexes, 'risky/secondary_indexes'
|
12
|
+
autoload :Timestamps, 'risky/timestamps'
|
13
|
+
autoload :Inflector, 'risky/inflector'
|
14
|
+
autoload :PaginatedCollection, 'risky/paginated_collection'
|
15
|
+
autoload :GZip, 'risky/gzip'
|
5
16
|
|
6
|
-
|
17
|
+
extend Enumerable
|
7
18
|
|
8
|
-
|
9
|
-
|
10
|
-
|
19
|
+
class << self
|
20
|
+
# Get a model by key. Returns nil if not found. You can also pass opts to
|
21
|
+
# #reload (e.g. :r, :merge => false).
|
22
|
+
def [](key, opts = {})
|
23
|
+
return nil unless key
|
11
24
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
25
|
+
begin
|
26
|
+
new(key).reload(opts)
|
27
|
+
rescue Riak::FailedRequest => e
|
28
|
+
raise e unless e.not_found?
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
end
|
19
32
|
|
20
|
-
|
33
|
+
def find(key, opts = {})
|
34
|
+
self[key.to_s, opts]
|
35
|
+
end
|
21
36
|
|
22
|
-
|
23
|
-
|
24
|
-
def self.[](key, opts = {})
|
25
|
-
return nil unless key
|
37
|
+
def find_all_by_key(keys)
|
38
|
+
return [] if keys.blank?
|
26
39
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
raise e unless e.not_found?
|
31
|
-
nil
|
40
|
+
keys.map!(&:to_s)
|
41
|
+
results = bucket.get_many(keys)
|
42
|
+
keys.map { |key| from_riak_object(results[key]) }.compact
|
32
43
|
end
|
33
|
-
end
|
34
44
|
|
35
|
-
|
36
|
-
|
37
|
-
def self.all(opts = {:reload => true})
|
38
|
-
bucket.keys(opts).map do |key|
|
39
|
-
self[key]
|
45
|
+
def create(key = nil, values = {}, opts = {})
|
46
|
+
new(key, values).save(opts)
|
40
47
|
end
|
41
|
-
end
|
42
48
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
49
|
+
# Indicates that this model may be multivalued; in which case .merge should
|
50
|
+
# also be defined.
|
51
|
+
def allow_mult
|
52
|
+
unless bucket.props['allow_mult']
|
53
|
+
bucket.props = bucket.props.merge('allow_mult' => true)
|
54
|
+
end
|
48
55
|
end
|
49
|
-
end
|
50
56
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
57
|
+
# The Riak::Bucket backing this model.
|
58
|
+
# If name is passed, *sets* the bucket name.
|
59
|
+
def bucket(name = nil)
|
60
|
+
if name
|
61
|
+
@bucket_name = name.to_s
|
62
|
+
end
|
63
|
+
|
64
|
+
riak.bucket(@bucket_name)
|
56
65
|
end
|
57
|
-
|
58
|
-
riak.bucket(@bucket_name)
|
59
|
-
end
|
60
66
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
67
|
+
# The string name of the bucket used for storing instances of this model.
|
68
|
+
def bucket_name
|
69
|
+
@bucket_name
|
70
|
+
end
|
65
71
|
|
66
|
-
|
67
|
-
|
68
|
-
|
72
|
+
def bucket_name=(bucket)
|
73
|
+
@bucket_name = name.to_s
|
74
|
+
end
|
69
75
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
76
|
+
def content_type
|
77
|
+
"application/json"
|
78
|
+
end
|
79
|
+
|
80
|
+
# Casts data to appropriate types for values.
|
81
|
+
def cast(data)
|
82
|
+
casted = {}
|
83
|
+
data.each do |k, v|
|
84
|
+
c = @values[k][:class] rescue nil
|
85
|
+
casted[k] = begin
|
86
|
+
if c == Time
|
87
|
+
Time.iso8601(v)
|
88
|
+
else
|
89
|
+
v
|
90
|
+
end
|
91
|
+
rescue
|
79
92
|
v
|
80
93
|
end
|
81
|
-
rescue
|
82
|
-
v
|
83
94
|
end
|
95
|
+
casted
|
84
96
|
end
|
85
|
-
casted
|
86
|
-
end
|
87
97
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
98
|
+
# Returns true when record deleted.
|
99
|
+
# Returns nil when record was not present to begin with.
|
100
|
+
def delete(key, opts = {})
|
101
|
+
return if key.nil?
|
102
|
+
bucket.delete(key.to_s, opts)
|
93
103
|
end
|
94
|
-
count
|
95
|
-
end
|
96
104
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
(bucket.delete(key.to_s, opts)[:code] == 204) or nil
|
102
|
-
end
|
103
|
-
|
104
|
-
# Iterate over all items using key streaming.
|
105
|
-
def self.each
|
106
|
-
bucket.keys do |keys|
|
107
|
-
keys.each do |key|
|
108
|
-
if x = self[key]
|
109
|
-
yield x
|
110
|
-
end
|
111
|
-
end
|
105
|
+
# Does the given key exist in our bucket?
|
106
|
+
def exists?(key)
|
107
|
+
return if key.nil?
|
108
|
+
bucket.exists? key.to_s
|
112
109
|
end
|
113
|
-
end
|
114
110
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
bucket.exists? key.to_s
|
119
|
-
end
|
111
|
+
# Fills in values from a Riak::RObject
|
112
|
+
def from_riak_object(riak_object)
|
113
|
+
return nil if riak_object.nil?
|
120
114
|
|
121
|
-
|
122
|
-
def self.from_riak_object(riak_object)
|
123
|
-
return nil if riak_object.nil?
|
115
|
+
n = new.load_riak_object riak_object
|
124
116
|
|
125
|
-
|
117
|
+
# Callback
|
118
|
+
n.after_load
|
119
|
+
n
|
120
|
+
end
|
126
121
|
|
127
|
-
#
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
keys.each do |key|
|
144
|
-
yield key
|
122
|
+
# Gets an existing record or creates one.
|
123
|
+
def get_or_new(*args)
|
124
|
+
self[*args] or new(args.first)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Establishes methods for manipulating a single link with a given tag.
|
128
|
+
def link(tag)
|
129
|
+
tag = tag.to_s
|
130
|
+
class_eval %Q{
|
131
|
+
def #{tag}
|
132
|
+
begin
|
133
|
+
@riak_object.links.find do |l|
|
134
|
+
l.tag == #{tag.inspect}
|
135
|
+
end.key
|
136
|
+
rescue NoMethodError
|
137
|
+
nil
|
145
138
|
end
|
146
|
-
else
|
147
|
-
yield keys
|
148
139
|
end
|
149
|
-
end
|
150
|
-
else
|
151
|
-
bucket.keys(*a)
|
152
|
-
end
|
153
|
-
end
|
154
140
|
|
155
|
-
|
156
|
-
|
157
|
-
tag = tag.to_s
|
158
|
-
class_eval "
|
159
|
-
def #{tag}
|
160
|
-
begin
|
161
|
-
@riak_object.links.find do |l|
|
141
|
+
def #{tag}=(link)
|
142
|
+
@riak_object.links.reject! do |l|
|
162
143
|
l.tag == #{tag.inspect}
|
163
|
-
end
|
164
|
-
|
165
|
-
|
144
|
+
end
|
145
|
+
if link
|
146
|
+
@riak_object.links << link.to_link(#{tag.inspect})
|
147
|
+
end
|
166
148
|
end
|
167
|
-
|
149
|
+
}
|
150
|
+
end
|
168
151
|
|
169
|
-
|
170
|
-
|
171
|
-
|
152
|
+
# Establishes methods for manipulating a set of links with a given tag.
|
153
|
+
def links(tag)
|
154
|
+
tag = tag.to_s
|
155
|
+
class_eval %Q{
|
156
|
+
def #{tag}
|
157
|
+
@riak_object.links.select do |l|
|
158
|
+
l.tag == #{tag.inspect}
|
159
|
+
end.map do |l|
|
160
|
+
l.key
|
161
|
+
end
|
172
162
|
end
|
173
|
-
|
163
|
+
|
164
|
+
def add_#{tag}(link)
|
174
165
|
@riak_object.links << link.to_link(#{tag.inspect})
|
175
166
|
end
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
# Establishes methods for manipulating a set of links with a given tag.
|
181
|
-
def self.links(tag)
|
182
|
-
tag = tag.to_s
|
183
|
-
class_eval "
|
184
|
-
def #{tag}
|
185
|
-
@riak_object.links.select do |l|
|
186
|
-
l.tag == #{tag.inspect}
|
187
|
-
end.map do |l|
|
188
|
-
l.key
|
167
|
+
|
168
|
+
def remove_#{tag}(link)
|
169
|
+
@riak_object.links.delete link.to_link(#{tag.inspect})
|
189
170
|
end
|
190
|
-
end
|
191
171
|
|
192
|
-
|
193
|
-
|
194
|
-
|
172
|
+
def clear_#{tag}
|
173
|
+
@riak_object.links.delete_if do |l|
|
174
|
+
l.tag == #{tag.inspect}
|
175
|
+
end
|
176
|
+
end
|
195
177
|
|
196
|
-
|
197
|
-
|
198
|
-
|
178
|
+
def #{tag}_count
|
179
|
+
@riak_object.links.select{|l| l.tag == #{tag.inspect}}.length
|
180
|
+
end
|
181
|
+
}
|
182
|
+
end
|
199
183
|
|
200
|
-
|
201
|
-
|
202
|
-
|
184
|
+
# Mapreduce helper
|
185
|
+
def map(*args)
|
186
|
+
mr.map(*args)
|
187
|
+
end
|
188
|
+
|
189
|
+
# Merges n versions of a record together, for read-repair.
|
190
|
+
# Returns the merged record.
|
191
|
+
def merge(versions)
|
192
|
+
versions.first
|
193
|
+
end
|
194
|
+
|
195
|
+
# Begins a mapreduce on this model's bucket.
|
196
|
+
# If no keys are given, operates on the entire bucket.
|
197
|
+
# If keys are given, operates on those keys first.
|
198
|
+
def mr(keys = nil)
|
199
|
+
mr = Riak::MapReduce.new(riak)
|
200
|
+
|
201
|
+
if keys
|
202
|
+
# Add specific keys
|
203
|
+
[*keys].compact.inject(mr) do |mr, key|
|
204
|
+
mr.add @bucket_name, key.to_s
|
203
205
|
end
|
206
|
+
else
|
207
|
+
# Add whole bucket
|
208
|
+
mr.add @bucket_name
|
204
209
|
end
|
205
|
-
|
206
|
-
def #{tag}_count
|
207
|
-
@riak_object.links.select{|l| l.tag == #{tag.inspect}}.length
|
208
|
-
end
|
209
|
-
"
|
210
|
-
end
|
210
|
+
end
|
211
211
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
212
|
+
# MR helper.
|
213
|
+
def reduce(*args)
|
214
|
+
mr.reduce(*args)
|
215
|
+
end
|
216
216
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
217
|
+
# The Riak::Client backing this model class.
|
218
|
+
def riak
|
219
|
+
Thread.current[:riak_client] ||=
|
220
|
+
if @riak and @riak.respond_to?(:call)
|
221
|
+
@riak.call(self)
|
222
|
+
elsif @riak
|
223
|
+
@riak
|
224
|
+
else
|
225
|
+
superclass.riak
|
226
|
+
end
|
227
|
+
end
|
222
228
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
229
|
+
# Forces Riak client for this thread to be reset.
|
230
|
+
# If your @riak proc can choose between multiple hosts, calling this on
|
231
|
+
# failure will allow subsequent requests to proceed on another host.
|
232
|
+
def riak!
|
233
|
+
Thread.current[:riak_client] = nil
|
234
|
+
riak
|
235
|
+
end
|
228
236
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
else
|
235
|
-
# Add whole bucket
|
236
|
-
mr.add @bucket_name
|
237
|
+
# Sets the Riak Client backing this model class. If client is a lambda (or
|
238
|
+
# anything responding to #call), it will be invoked to generate a new client
|
239
|
+
# every time Risky feels it is appropriate.
|
240
|
+
def riak=(client)
|
241
|
+
@riak = client
|
237
242
|
end
|
238
|
-
end
|
239
243
|
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
+
# Add a new value to this model. Values aren't necessary; you can
|
245
|
+
# use Risky#[], but if you would like to cast values to/from JSON or
|
246
|
+
# specify defaults, you may:
|
247
|
+
#
|
248
|
+
# :default => object (#clone is called for each new instance)
|
249
|
+
# :class => Time, Integer, etc. Inferred from default.class if present.
|
250
|
+
def value(value, opts = {})
|
251
|
+
value = value.to_s
|
252
|
+
|
253
|
+
klass = if opts[:class]
|
254
|
+
opts[:class]
|
255
|
+
elsif opts.include? :default
|
256
|
+
opts[:default].class
|
257
|
+
else
|
258
|
+
nil
|
259
|
+
end
|
260
|
+
values[value] = opts.merge(:class => klass)
|
244
261
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
superclass.riak
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
|
-
# Forces this model's Riak client to be reset.
|
259
|
-
# If your @riak proc can choose between multiple hosts, calling this on
|
260
|
-
# failure will allow subsequent requests to proceed on another host.
|
261
|
-
def self.riak!
|
262
|
-
@riak_client = nil
|
263
|
-
riak
|
264
|
-
end
|
265
|
-
|
266
|
-
# Sets the Riak Client backing this model class. If client is a lambda (or
|
267
|
-
# anything responding to #call), it will be invoked to generate a new client
|
268
|
-
# every time Risky feels it is appropriate.
|
269
|
-
def self.riak=(client)
|
270
|
-
@riak = client
|
271
|
-
end
|
272
|
-
|
273
|
-
# Add a new value to this model. Values aren't necessary; you can
|
274
|
-
# use Risky#[], but if you would like to cast values to/from JSON or
|
275
|
-
# specify defaults, you may:
|
276
|
-
#
|
277
|
-
# :default => object (#clone is called for each new instance)
|
278
|
-
# :class => Time, Integer, etc. Inferred from default.class if present.
|
279
|
-
def self.value(value, opts = {})
|
280
|
-
value = value.to_s
|
281
|
-
|
282
|
-
klass = if opts[:class]
|
283
|
-
opts[:class]
|
284
|
-
elsif opts.include? :default
|
285
|
-
opts[:default].class
|
286
|
-
else
|
287
|
-
nil
|
262
|
+
class_eval %Q{
|
263
|
+
def #{value}
|
264
|
+
@values[#{value.inspect}]
|
265
|
+
end
|
266
|
+
|
267
|
+
def #{value}=(value)
|
268
|
+
@values[#{value.inspect}] = value
|
269
|
+
end
|
270
|
+
}
|
288
271
|
end
|
289
|
-
values[value] = opts.merge(:class => klass)
|
290
|
-
|
291
|
-
class_eval "
|
292
|
-
def #{value}; @values[#{value.inspect}]; end
|
293
|
-
def #{value}=(value); @values[#{value.inspect}] = value; end
|
294
|
-
"
|
295
|
-
end
|
296
272
|
|
297
|
-
|
298
|
-
|
299
|
-
|
273
|
+
# A list of all values we track.
|
274
|
+
def values
|
275
|
+
@values ||= {}
|
276
|
+
end
|
300
277
|
end
|
301
278
|
|
302
279
|
|
@@ -317,7 +294,7 @@ class Risky
|
|
317
294
|
key = key.to_s unless key.nil?
|
318
295
|
|
319
296
|
@riak_object ||= Riak::RObject.new(self.class.bucket, key)
|
320
|
-
@riak_object.content_type =
|
297
|
+
@riak_object.content_type = self.class.content_type
|
321
298
|
|
322
299
|
@new = true
|
323
300
|
@merged = false
|
@@ -337,22 +314,27 @@ class Risky
|
|
337
314
|
if self[k].nil?
|
338
315
|
self[k] = (v[:default].clone rescue v[:default])
|
339
316
|
end
|
340
|
-
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def ==(object)
|
321
|
+
object.class == self.class &&
|
322
|
+
(object.key.present? && object.key == self.key || object.object_id == self.object_id)
|
341
323
|
end
|
324
|
+
alias :eql? :==
|
342
325
|
|
343
|
-
|
344
|
-
|
345
|
-
o.class == self.class and o.key.to_s == self.key.to_s rescue false
|
326
|
+
def ===(object)
|
327
|
+
object.is_a?(self.class)
|
346
328
|
end
|
347
329
|
|
348
330
|
# Access the values hash.
|
349
331
|
def [](k)
|
350
|
-
|
332
|
+
values[k]
|
351
333
|
end
|
352
334
|
|
353
335
|
# Access the values hash.
|
354
336
|
def []=(k, v)
|
355
|
-
|
337
|
+
values[k] = v
|
356
338
|
end
|
357
339
|
|
358
340
|
def after_create
|
@@ -371,7 +353,7 @@ class Risky
|
|
371
353
|
def as_json(opts = {})
|
372
354
|
h = @values.merge(:key => key)
|
373
355
|
h[:errors] = errors unless errors.empty?
|
374
|
-
h
|
356
|
+
h
|
375
357
|
end
|
376
358
|
|
377
359
|
# Called before creation and validation
|
@@ -406,7 +388,12 @@ class Risky
|
|
406
388
|
# Engage conflict resolution mode
|
407
389
|
final = self.class.merge(
|
408
390
|
siblings.map do |sibling|
|
409
|
-
|
391
|
+
robject = Riak::RObject.new(sibling.bucket, sibling.key)
|
392
|
+
robject.raw_data = sibling.raw_data
|
393
|
+
robject.content_type = sibling.content_type
|
394
|
+
# robject.siblings = [sibling]
|
395
|
+
robject.vclock = sibling.vclock
|
396
|
+
self.class.new.load_riak_object(robject, :merge => false)
|
410
397
|
end
|
411
398
|
)
|
412
399
|
|
@@ -418,7 +405,7 @@ class Risky
|
|
418
405
|
self.merged = true
|
419
406
|
else
|
420
407
|
# Not merging
|
421
|
-
self.values = self.class.cast(
|
408
|
+
self.values = self.class.cast(riak_object.data) rescue {}
|
422
409
|
self.class.values.each do |k, v|
|
423
410
|
if values[k].nil?
|
424
411
|
values[k] = (v[:default].clone rescue v[:default])
|
@@ -428,7 +415,7 @@ class Risky
|
|
428
415
|
self.new = false
|
429
416
|
self.merged = false
|
430
417
|
end
|
431
|
-
|
418
|
+
|
432
419
|
self
|
433
420
|
end
|
434
421
|
|
@@ -441,13 +428,23 @@ class Risky
|
|
441
428
|
@riak_object.key = nil
|
442
429
|
else
|
443
430
|
@riak_object.key = key.to_s
|
444
|
-
end
|
431
|
+
end
|
445
432
|
end
|
446
433
|
|
447
434
|
def key
|
448
435
|
@riak_object.key
|
449
436
|
end
|
450
437
|
|
438
|
+
def id
|
439
|
+
Integer(key)
|
440
|
+
rescue ArgumentError
|
441
|
+
key
|
442
|
+
end
|
443
|
+
|
444
|
+
def id=(value)
|
445
|
+
self.key = value
|
446
|
+
end
|
447
|
+
|
451
448
|
def merged=(merged)
|
452
449
|
@merged = !!merged
|
453
450
|
end
|
@@ -481,7 +478,7 @@ class Risky
|
|
481
478
|
end
|
482
479
|
|
483
480
|
# Saves this model.
|
484
|
-
#
|
481
|
+
#
|
485
482
|
# Calls #validate and #valid? unless :validate is false.
|
486
483
|
#
|
487
484
|
# Converts @values to_json and saves it to riak.
|
@@ -495,9 +492,9 @@ class Risky
|
|
495
492
|
return false unless valid?
|
496
493
|
end
|
497
494
|
|
498
|
-
@riak_object.
|
499
|
-
|
500
|
-
|
495
|
+
@riak_object.data = @values
|
496
|
+
@riak_object.content_type = self.class.content_type
|
497
|
+
|
501
498
|
store_opts = {}
|
502
499
|
store_opts[:w] = opts[:w] if opts[:w]
|
503
500
|
store_opts[:dw] = opts[:dw] if opts[:dw]
|
@@ -511,6 +508,18 @@ class Risky
|
|
511
508
|
self
|
512
509
|
end
|
513
510
|
|
511
|
+
def update_attribute(attribute, value)
|
512
|
+
self.send("#{attribute}=", value)
|
513
|
+
self.save
|
514
|
+
end
|
515
|
+
|
516
|
+
def update_attributes(attributes)
|
517
|
+
attributes.each do |attribute, value|
|
518
|
+
self.send("#{attribute}=", value)
|
519
|
+
end
|
520
|
+
self.save
|
521
|
+
end
|
522
|
+
|
514
523
|
# This is provided for convenience; #save does *not* use this method, and you
|
515
524
|
# are free to override it.
|
516
525
|
def to_json(*a)
|
@@ -527,10 +536,10 @@ class Risky
|
|
527
536
|
@errors = {}
|
528
537
|
validate
|
529
538
|
@errors.empty?
|
530
|
-
end
|
531
|
-
|
539
|
+
end
|
540
|
+
|
532
541
|
# Determines whether the model is valid. Sets the contents of #errors if
|
533
|
-
# invalid.
|
542
|
+
# invalid.
|
534
543
|
def validate
|
535
544
|
if key.blank?
|
536
545
|
errors[:key] = 'is missing'
|