risky 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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'
|