zermelo 1.1.0 → 1.2.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 +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +76 -52
- data/lib/zermelo/associations/association_data.rb +4 -3
- data/lib/zermelo/associations/class_methods.rb +37 -50
- data/lib/zermelo/associations/index.rb +3 -1
- data/lib/zermelo/associations/multiple.rb +247 -0
- data/lib/zermelo/associations/range_index.rb +44 -0
- data/lib/zermelo/associations/singular.rb +193 -0
- data/lib/zermelo/associations/unique_index.rb +4 -3
- data/lib/zermelo/backend.rb +120 -0
- data/lib/zermelo/backends/{influxdb_backend.rb → influxdb.rb} +87 -31
- data/lib/zermelo/backends/{redis_backend.rb → redis.rb} +53 -58
- data/lib/zermelo/backends/stub.rb +43 -0
- data/lib/zermelo/filter.rb +194 -0
- data/lib/zermelo/filters/index_range.rb +22 -0
- data/lib/zermelo/filters/{influxdb_filter.rb → influxdb.rb} +12 -11
- data/lib/zermelo/filters/redis.rb +173 -0
- data/lib/zermelo/filters/steps/list_step.rb +48 -30
- data/lib/zermelo/filters/steps/set_step.rb +148 -89
- data/lib/zermelo/filters/steps/sort_step.rb +2 -2
- data/lib/zermelo/record.rb +53 -0
- data/lib/zermelo/records/attributes.rb +32 -0
- data/lib/zermelo/records/class_methods.rb +12 -25
- data/lib/zermelo/records/{influxdb_record.rb → influxdb.rb} +3 -4
- data/lib/zermelo/records/instance_methods.rb +9 -8
- data/lib/zermelo/records/key.rb +3 -1
- data/lib/zermelo/records/redis.rb +17 -0
- data/lib/zermelo/records/stub.rb +17 -0
- data/lib/zermelo/version.rb +1 -1
- data/spec/lib/zermelo/associations/index_spec.rb +70 -1
- data/spec/lib/zermelo/associations/multiple_spec.rb +1084 -0
- data/spec/lib/zermelo/associations/range_index_spec.rb +77 -0
- data/spec/lib/zermelo/associations/singular_spec.rb +149 -0
- data/spec/lib/zermelo/associations/unique_index_spec.rb +58 -2
- data/spec/lib/zermelo/filter_spec.rb +363 -0
- data/spec/lib/zermelo/locks/redis_lock_spec.rb +3 -3
- data/spec/lib/zermelo/records/instance_methods_spec.rb +206 -0
- data/spec/spec_helper.rb +9 -1
- data/spec/support/mock_logger.rb +48 -0
- metadata +31 -46
- data/lib/zermelo/associations/belongs_to.rb +0 -115
- data/lib/zermelo/associations/has_and_belongs_to_many.rb +0 -128
- data/lib/zermelo/associations/has_many.rb +0 -120
- data/lib/zermelo/associations/has_one.rb +0 -109
- data/lib/zermelo/associations/has_sorted_set.rb +0 -124
- data/lib/zermelo/backends/base.rb +0 -115
- data/lib/zermelo/filters/base.rb +0 -212
- data/lib/zermelo/filters/redis_filter.rb +0 -111
- data/lib/zermelo/filters/steps/sorted_set_step.rb +0 -156
- data/lib/zermelo/records/base.rb +0 -62
- data/lib/zermelo/records/redis_record.rb +0 -27
- data/spec/lib/zermelo/associations/belongs_to_spec.rb +0 -6
- data/spec/lib/zermelo/associations/has_many_spec.rb +0 -6
- data/spec/lib/zermelo/associations/has_one_spec.rb +0 -6
- data/spec/lib/zermelo/associations/has_sorted_set.spec.rb +0 -6
- data/spec/lib/zermelo/backends/influxdb_backend_spec.rb +0 -0
- data/spec/lib/zermelo/backends/moneta_backend_spec.rb +0 -0
- data/spec/lib/zermelo/filters/influxdb_filter_spec.rb +0 -0
- data/spec/lib/zermelo/filters/redis_filter_spec.rb +0 -0
- data/spec/lib/zermelo/records/influxdb_record_spec.rb +0 -434
- data/spec/lib/zermelo/records/key_spec.rb +0 -6
- data/spec/lib/zermelo/records/redis_record_spec.rb +0 -1461
- data/spec/lib/zermelo/records/type_validator_spec.rb +0 -6
- data/spec/lib/zermelo/version_spec.rb +0 -6
- data/spec/lib/zermelo_spec.rb +0 -6
@@ -0,0 +1,206 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'zermelo/records/redis'
|
3
|
+
require 'zermelo/records/influxdb'
|
4
|
+
|
5
|
+
describe Zermelo::Records::InstMethods do
|
6
|
+
|
7
|
+
shared_examples "it supports ActiveModel instance methods", :instance_methods => true do
|
8
|
+
|
9
|
+
it "is invalid without a name" do
|
10
|
+
example = example_class.new(:id => '1')
|
11
|
+
expect(example).not_to be_valid
|
12
|
+
|
13
|
+
errs = example.errors
|
14
|
+
expect(errs).not_to be_nil
|
15
|
+
expect(errs[:name]).to eq(["can't be blank"])
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'saves a record' do
|
19
|
+
example = example_class.new(:id => '1', :name => 'John Smith')
|
20
|
+
expect(example).to be_valid
|
21
|
+
expect(example_class.count).to eq(0)
|
22
|
+
expect(example.save).to be true
|
23
|
+
expect(example_class.count).to eq(1)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "updates a value" do
|
27
|
+
create_example(:id => '1', :name => 'Jane Doe')
|
28
|
+
|
29
|
+
example = example_class.find_by_id('1')
|
30
|
+
expect(example).not_to be_nil
|
31
|
+
|
32
|
+
example.name = 'John Smith'
|
33
|
+
example.save
|
34
|
+
|
35
|
+
other_example = example_class.find_by_id('1')
|
36
|
+
expect(other_example).not_to be_nil
|
37
|
+
expect(other_example.name).to eq('John Smith')
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'raises an RecordInvalid exception if validation fails while saving' do
|
41
|
+
example = example_class.new(:id => '1')
|
42
|
+
|
43
|
+
expect {
|
44
|
+
example.save!
|
45
|
+
}.to raise_error(Zermelo::Records::Errors::RecordInvalid)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'raises a RecordNotSaved exception if a callback blocks saving' do
|
49
|
+
example = example_class.new(:id => '1', :name => 'not_saving')
|
50
|
+
|
51
|
+
expect {
|
52
|
+
example.save!
|
53
|
+
}.to raise_error(Zermelo::Records::Errors::RecordNotSaved)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "resets changed state on refresh" do
|
57
|
+
create_example(:id => '8', :name => 'John Jones')
|
58
|
+
example = example_class.find_by_id('8')
|
59
|
+
|
60
|
+
example.name = "King Henry VIII"
|
61
|
+
expect(example.changed).to include('name')
|
62
|
+
expect(example.changes).to eq({'name' => ['John Jones', 'King Henry VIII']})
|
63
|
+
|
64
|
+
example.refresh
|
65
|
+
expect(example.changed).to be_empty
|
66
|
+
expect(example.changes).to be_empty
|
67
|
+
end
|
68
|
+
|
69
|
+
it "stores a string as an attribute value"
|
70
|
+
it "stores an integer as an attribute value"
|
71
|
+
it "stores a timestamp as an attribute value"
|
72
|
+
it "stores a boolean as an attribute value"
|
73
|
+
|
74
|
+
it "stores a list as an attribute value"
|
75
|
+
it "stores a set as an attribute value"
|
76
|
+
it "stores a hash as an attribute value"
|
77
|
+
|
78
|
+
it 'destroys a record' do
|
79
|
+
create_example(:id => '1', :name => 'Jane Doe')
|
80
|
+
|
81
|
+
example = example_class.find_by_id('1')
|
82
|
+
example.destroy
|
83
|
+
example_chk = example_class.find_by_id('1')
|
84
|
+
expect(example_chk).to be_nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'redis', :redis => true, :instance_methods => true do
|
89
|
+
|
90
|
+
module ZermeloExamples
|
91
|
+
class InstanceMethodsRedis
|
92
|
+
include Zermelo::Records::Redis
|
93
|
+
|
94
|
+
define_attributes :name => :string
|
95
|
+
validates :name, :presence => true
|
96
|
+
|
97
|
+
before_create :fail_if_not_saving
|
98
|
+
def fail_if_not_saving; !('not_saving'.eql?(self.name)); end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
let(:redis) { Zermelo.redis }
|
103
|
+
|
104
|
+
let(:example_class) { ZermeloExamples::InstanceMethodsRedis }
|
105
|
+
|
106
|
+
let(:ek) { 'instance_methods_redis' }
|
107
|
+
|
108
|
+
def create_example(attrs = {})
|
109
|
+
redis.hmset("#{ek}:#{attrs[:id]}:attrs", {'name' => attrs[:name]}.to_a.flatten)
|
110
|
+
redis.sadd("#{ek}::attrs:ids", attrs[:id])
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'creates data on record save' do
|
114
|
+
example = example_class.new(:id => '1', :name => 'John Smith')
|
115
|
+
expect(example).to be_valid
|
116
|
+
expect(example.save).to be true
|
117
|
+
|
118
|
+
expect(redis.keys('*')).to match_array([
|
119
|
+
"#{ek}::attrs:ids",
|
120
|
+
"#{ek}:1:attrs"
|
121
|
+
])
|
122
|
+
expect(redis.smembers("#{ek}::attrs:ids")).to eq(['1'])
|
123
|
+
expect(redis.hgetall("#{ek}:1:attrs")).to eq(
|
124
|
+
'name' => 'John Smith'
|
125
|
+
)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "updates a record's attributes" do
|
129
|
+
create_example(:id => '8', :name => 'John Jones')
|
130
|
+
|
131
|
+
example = example_class.find_by_id('8')
|
132
|
+
example.name = 'Jane Janes'
|
133
|
+
expect(example.save).to be true
|
134
|
+
|
135
|
+
expect(redis.keys('*')).to match_array([
|
136
|
+
"#{ek}::attrs:ids",
|
137
|
+
"#{ek}:8:attrs"
|
138
|
+
])
|
139
|
+
expect(redis.smembers("#{ek}::attrs:ids")).to eq(['8'])
|
140
|
+
expect(redis.hgetall("#{ek}:8:attrs")).to eq(
|
141
|
+
'name' => 'Jane Janes'
|
142
|
+
)
|
143
|
+
end
|
144
|
+
|
145
|
+
it "deletes a record's attributes" do
|
146
|
+
create_example(:id => '8', :name => 'John Jones')
|
147
|
+
|
148
|
+
expect(redis.keys('*')).to match_array([
|
149
|
+
"#{ek}::attrs:ids",
|
150
|
+
"#{ek}:8:attrs",
|
151
|
+
])
|
152
|
+
|
153
|
+
example = example_class.find_by_id('8')
|
154
|
+
example.destroy
|
155
|
+
|
156
|
+
expect(redis.keys('*')).to eq([])
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
context 'influxdb', :influxdb => true, :instance_methods => true do
|
162
|
+
|
163
|
+
module ZermeloExamples
|
164
|
+
class InstanceMethodsInfluxDB
|
165
|
+
include Zermelo::Records::InfluxDB
|
166
|
+
|
167
|
+
define_attributes :name => :string
|
168
|
+
validates :name, :presence => true
|
169
|
+
|
170
|
+
before_create :fail_if_not_saving
|
171
|
+
def fail_if_not_saving; !('not_saving'.eql?(self.name)); end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
let(:influxdb) { Zermelo.influxdb }
|
176
|
+
|
177
|
+
let(:example_class) { ZermeloExamples::InstanceMethodsInfluxDB }
|
178
|
+
|
179
|
+
let(:ek) { 'instance_methods_influx_db' }
|
180
|
+
|
181
|
+
def create_example(attrs = {})
|
182
|
+
Zermelo.influxdb.write_point("#{ek}/#{attrs[:id]}", attrs)
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'creates data on record save' do
|
186
|
+
begin
|
187
|
+
data = Zermelo.influxdb.query("select * from /#{ek}\\/1/")["#{ek}/1"]
|
188
|
+
expect(data).to be_nil
|
189
|
+
rescue InfluxDB::Error => ide
|
190
|
+
# only happens occasionally, with an empty time series by that name
|
191
|
+
raise unless /^Couldn't look up columns$/ === ide.message
|
192
|
+
end
|
193
|
+
|
194
|
+
example = example_class.new(:id => '1', :name => 'John Smith')
|
195
|
+
expect(example).to be_valid
|
196
|
+
expect(example.save).to be true
|
197
|
+
|
198
|
+
data = Zermelo.influxdb.query("select * from /#{ek}\\/1/")["#{ek}/1"]
|
199
|
+
expect(data).to be_an(Array)
|
200
|
+
expect(data.size).to eql(1)
|
201
|
+
record = data.first
|
202
|
+
expect(record).to be_a(Hash)
|
203
|
+
expect(record).to include("name"=>"John Smith", "id"=>"1")
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -36,7 +36,15 @@ RSpec.configure do |config|
|
|
36
36
|
# order dependency and want to debug it, you can fix the order by providing
|
37
37
|
# the seed, which is printed after each run.
|
38
38
|
# --seed 1234
|
39
|
-
config.order = 'random'
|
39
|
+
# config.order = 'random'
|
40
|
+
|
41
|
+
config.around(:each, :logger => true) do |example|
|
42
|
+
MockLogger.configure_log('zermelo')
|
43
|
+
Zermelo.logger = MockLogger.new
|
44
|
+
example.run
|
45
|
+
puts Zermelo.logger.messages.compact.join("\n")
|
46
|
+
Zermelo.logger.clear
|
47
|
+
end
|
40
48
|
|
41
49
|
config.before(:all, :redis => true) do
|
42
50
|
Zermelo.redis = ::Redis.new(:db => 14)
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class MockLogger
|
2
|
+
attr_accessor :messages, :errors
|
3
|
+
|
4
|
+
def self.configure_log(name, config = {})
|
5
|
+
@name = name
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@messages = []
|
10
|
+
@errors = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def clear
|
14
|
+
@messages.clear
|
15
|
+
@errors.clear
|
16
|
+
end
|
17
|
+
|
18
|
+
%w(debug info warn).each do |level|
|
19
|
+
class_eval <<-RUBY
|
20
|
+
def #{level}(msg = nil, &block)
|
21
|
+
msg = yield if msg.nil? && block_given?
|
22
|
+
@messages << '[#{level.upcase}] :: ' +
|
23
|
+
(self.class.instance_variable_get('@name') || 'flapjack') + ' :: ' + msg
|
24
|
+
end
|
25
|
+
RUBY
|
26
|
+
end
|
27
|
+
|
28
|
+
%w(error fatal).each do |level|
|
29
|
+
class_eval <<-ERRORS
|
30
|
+
def #{level}(msg = nil, &block)
|
31
|
+
msg = yield if msg.nil? && block_given?
|
32
|
+
@messages << '[#{level.upcase}] :: ' +
|
33
|
+
(self.class.instance_variable_get('@name') || 'flapjack') + ' :: ' + msg
|
34
|
+
@errors << '[#{level.upcase}] :: ' +
|
35
|
+
(self.class.instance_variable_get('@name') || 'flapjack') + ' :: ' + msg
|
36
|
+
end
|
37
|
+
ERRORS
|
38
|
+
end
|
39
|
+
|
40
|
+
%w(debug info warn error fatal).each do |level|
|
41
|
+
class_eval <<-LEVELS
|
42
|
+
def #{level}?
|
43
|
+
true
|
44
|
+
end
|
45
|
+
LEVELS
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zermelo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ali Graham
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-06-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -69,55 +69,48 @@ files:
|
|
69
69
|
- Rakefile
|
70
70
|
- lib/zermelo.rb
|
71
71
|
- lib/zermelo/associations/association_data.rb
|
72
|
-
- lib/zermelo/associations/belongs_to.rb
|
73
72
|
- lib/zermelo/associations/class_methods.rb
|
74
|
-
- lib/zermelo/associations/has_and_belongs_to_many.rb
|
75
|
-
- lib/zermelo/associations/has_many.rb
|
76
|
-
- lib/zermelo/associations/has_one.rb
|
77
|
-
- lib/zermelo/associations/has_sorted_set.rb
|
78
73
|
- lib/zermelo/associations/index.rb
|
79
74
|
- lib/zermelo/associations/index_data.rb
|
75
|
+
- lib/zermelo/associations/multiple.rb
|
76
|
+
- lib/zermelo/associations/range_index.rb
|
77
|
+
- lib/zermelo/associations/singular.rb
|
80
78
|
- lib/zermelo/associations/unique_index.rb
|
81
|
-
- lib/zermelo/
|
82
|
-
- lib/zermelo/backends/
|
83
|
-
- lib/zermelo/backends/
|
84
|
-
- lib/zermelo/
|
85
|
-
- lib/zermelo/
|
86
|
-
- lib/zermelo/filters/
|
79
|
+
- lib/zermelo/backend.rb
|
80
|
+
- lib/zermelo/backends/influxdb.rb
|
81
|
+
- lib/zermelo/backends/redis.rb
|
82
|
+
- lib/zermelo/backends/stub.rb
|
83
|
+
- lib/zermelo/filter.rb
|
84
|
+
- lib/zermelo/filters/index_range.rb
|
85
|
+
- lib/zermelo/filters/influxdb.rb
|
86
|
+
- lib/zermelo/filters/redis.rb
|
87
87
|
- lib/zermelo/filters/steps/base_step.rb
|
88
88
|
- lib/zermelo/filters/steps/list_step.rb
|
89
89
|
- lib/zermelo/filters/steps/set_step.rb
|
90
90
|
- lib/zermelo/filters/steps/sort_step.rb
|
91
|
-
- lib/zermelo/filters/steps/sorted_set_step.rb
|
92
91
|
- lib/zermelo/locks/no_lock.rb
|
93
92
|
- lib/zermelo/locks/redis_lock.rb
|
94
|
-
- lib/zermelo/
|
93
|
+
- lib/zermelo/record.rb
|
94
|
+
- lib/zermelo/records/attributes.rb
|
95
95
|
- lib/zermelo/records/class_methods.rb
|
96
96
|
- lib/zermelo/records/errors.rb
|
97
|
-
- lib/zermelo/records/
|
97
|
+
- lib/zermelo/records/influxdb.rb
|
98
98
|
- lib/zermelo/records/instance_methods.rb
|
99
99
|
- lib/zermelo/records/key.rb
|
100
|
-
- lib/zermelo/records/
|
100
|
+
- lib/zermelo/records/redis.rb
|
101
|
+
- lib/zermelo/records/stub.rb
|
101
102
|
- lib/zermelo/records/type_validator.rb
|
102
103
|
- lib/zermelo/version.rb
|
103
|
-
- spec/lib/zermelo/associations/belongs_to_spec.rb
|
104
|
-
- spec/lib/zermelo/associations/has_many_spec.rb
|
105
|
-
- spec/lib/zermelo/associations/has_one_spec.rb
|
106
|
-
- spec/lib/zermelo/associations/has_sorted_set.spec.rb
|
107
104
|
- spec/lib/zermelo/associations/index_spec.rb
|
105
|
+
- spec/lib/zermelo/associations/multiple_spec.rb
|
106
|
+
- spec/lib/zermelo/associations/range_index_spec.rb
|
107
|
+
- spec/lib/zermelo/associations/singular_spec.rb
|
108
108
|
- spec/lib/zermelo/associations/unique_index_spec.rb
|
109
|
-
- spec/lib/zermelo/
|
110
|
-
- spec/lib/zermelo/backends/moneta_backend_spec.rb
|
111
|
-
- spec/lib/zermelo/filters/influxdb_filter_spec.rb
|
112
|
-
- spec/lib/zermelo/filters/redis_filter_spec.rb
|
109
|
+
- spec/lib/zermelo/filter_spec.rb
|
113
110
|
- spec/lib/zermelo/locks/redis_lock_spec.rb
|
114
|
-
- spec/lib/zermelo/records/
|
115
|
-
- spec/lib/zermelo/records/key_spec.rb
|
116
|
-
- spec/lib/zermelo/records/redis_record_spec.rb
|
117
|
-
- spec/lib/zermelo/records/type_validator_spec.rb
|
118
|
-
- spec/lib/zermelo/version_spec.rb
|
119
|
-
- spec/lib/zermelo_spec.rb
|
111
|
+
- spec/lib/zermelo/records/instance_methods_spec.rb
|
120
112
|
- spec/spec_helper.rb
|
113
|
+
- spec/support/mock_logger.rb
|
121
114
|
- spec/support/profile_all_formatter.rb
|
122
115
|
- spec/support/uncolored_doc_formatter.rb
|
123
116
|
- zermelo.gemspec
|
@@ -142,28 +135,20 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
142
135
|
requirements:
|
143
136
|
- Redis and/or InfluxDB, and the related gems
|
144
137
|
rubyforge_project:
|
145
|
-
rubygems_version: 2.4.
|
138
|
+
rubygems_version: 2.4.8
|
146
139
|
signing_key:
|
147
140
|
specification_version: 4
|
148
141
|
summary: ActiveModel-based set-theoretic ORM for Redis/InfluxDB
|
149
142
|
test_files:
|
150
|
-
- spec/lib/zermelo/associations/belongs_to_spec.rb
|
151
|
-
- spec/lib/zermelo/associations/has_many_spec.rb
|
152
|
-
- spec/lib/zermelo/associations/has_one_spec.rb
|
153
|
-
- spec/lib/zermelo/associations/has_sorted_set.spec.rb
|
154
143
|
- spec/lib/zermelo/associations/index_spec.rb
|
144
|
+
- spec/lib/zermelo/associations/multiple_spec.rb
|
145
|
+
- spec/lib/zermelo/associations/range_index_spec.rb
|
146
|
+
- spec/lib/zermelo/associations/singular_spec.rb
|
155
147
|
- spec/lib/zermelo/associations/unique_index_spec.rb
|
156
|
-
- spec/lib/zermelo/
|
157
|
-
- spec/lib/zermelo/backends/moneta_backend_spec.rb
|
158
|
-
- spec/lib/zermelo/filters/influxdb_filter_spec.rb
|
159
|
-
- spec/lib/zermelo/filters/redis_filter_spec.rb
|
148
|
+
- spec/lib/zermelo/filter_spec.rb
|
160
149
|
- spec/lib/zermelo/locks/redis_lock_spec.rb
|
161
|
-
- spec/lib/zermelo/records/
|
162
|
-
- spec/lib/zermelo/records/key_spec.rb
|
163
|
-
- spec/lib/zermelo/records/redis_record_spec.rb
|
164
|
-
- spec/lib/zermelo/records/type_validator_spec.rb
|
165
|
-
- spec/lib/zermelo/version_spec.rb
|
166
|
-
- spec/lib/zermelo_spec.rb
|
150
|
+
- spec/lib/zermelo/records/instance_methods_spec.rb
|
167
151
|
- spec/spec_helper.rb
|
152
|
+
- spec/support/mock_logger.rb
|
168
153
|
- spec/support/profile_all_formatter.rb
|
169
154
|
- spec/support/uncolored_doc_formatter.rb
|
@@ -1,115 +0,0 @@
|
|
1
|
-
# The other side of a has_one, has_many, or has_sorted_set association
|
2
|
-
|
3
|
-
module Zermelo
|
4
|
-
module Associations
|
5
|
-
class BelongsTo
|
6
|
-
|
7
|
-
# NB a single instance of this class doesn't need to care about the hash
|
8
|
-
# used for storage, that should be done in the save method of the parent
|
9
|
-
|
10
|
-
def initialize(parent, name)
|
11
|
-
@parent = parent
|
12
|
-
@name = name
|
13
|
-
|
14
|
-
@backend = parent.send(:backend)
|
15
|
-
|
16
|
-
@record_ids_key = Zermelo::Records::Key.new(
|
17
|
-
:klass => parent.class,
|
18
|
-
:id => parent.id,
|
19
|
-
:name => 'belongs_to',
|
20
|
-
:type => :hash,
|
21
|
-
:object => :association
|
22
|
-
)
|
23
|
-
|
24
|
-
parent.class.send(:with_association_data, name.to_sym) do |data|
|
25
|
-
@associated_class = data.data_klass
|
26
|
-
@lock_klasses = [data.data_klass] + data.related_klasses
|
27
|
-
@inverse = data.inverse
|
28
|
-
@callbacks = data.callbacks
|
29
|
-
end
|
30
|
-
|
31
|
-
raise ':inverse_of must be set' if @inverse.nil?
|
32
|
-
@inverse_key = "#{name}_id"
|
33
|
-
end
|
34
|
-
|
35
|
-
def value=(record)
|
36
|
-
if record.nil?
|
37
|
-
@parent.class.lock(*@lock_klasses) do
|
38
|
-
r = @associated_class.send(:load, @backend.get(@record_ids_key)[@inverse_key.to_s])
|
39
|
-
bc = @callbacks[:before_clear]
|
40
|
-
if bc.nil? || !@parent.respond_to?(bc) || !@parent.send(bc, r).is_a?(FalseClass)
|
41
|
-
new_txn = @backend.begin_transaction
|
42
|
-
@backend.delete(@record_ids_key, @inverse_key)
|
43
|
-
@backend.commit_transaction if new_txn
|
44
|
-
ac = @callbacks[:after_clear]
|
45
|
-
@parent.send(ac, r) if !ac.nil? && @parent.respond_to?(ac)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
else
|
49
|
-
raise 'Invalid record class' unless record.is_a?(@associated_class)
|
50
|
-
raise 'Record must have been saved' unless record.persisted?
|
51
|
-
@parent.class.lock(*@lock_klasses) do
|
52
|
-
bs = @callbacks[:before_set]
|
53
|
-
if bs.nil? || !@parent.respond_to?(bs) || !@parent.send(bs, r).is_a?(FalseClass)
|
54
|
-
new_txn = @backend.begin_transaction
|
55
|
-
@backend.add(@record_ids_key, @inverse_key => record.id)
|
56
|
-
@backend.commit_transaction if new_txn
|
57
|
-
as = @callbacks[:after_set]
|
58
|
-
@parent.send(as, record) if !as.nil? && @parent.respond_to?(as)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def value
|
65
|
-
@parent.class.lock(*@lock_klasses) do
|
66
|
-
# FIXME uses hgetall, need separate getter for hash/list/set
|
67
|
-
if id = @backend.get(@record_ids_key)[@inverse_key.to_s]
|
68
|
-
# if id = @backend.get_hash_value(@record_ids_key, @inverse_key.to_s)
|
69
|
-
@associated_class.send(:load, id)
|
70
|
-
else
|
71
|
-
nil
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
private
|
77
|
-
|
78
|
-
# on_remove already runs inside a lock & transaction
|
79
|
-
def on_remove
|
80
|
-
unless value.nil?
|
81
|
-
assoc = value.send("#{@inverse}_proxy".to_sym)
|
82
|
-
if assoc.respond_to?(:delete)
|
83
|
-
assoc.send(:delete, @parent)
|
84
|
-
elsif assoc.respond_to?(:value=)
|
85
|
-
assoc.send(:value=, nil)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
@backend.clear(@record_ids_key)
|
89
|
-
end
|
90
|
-
|
91
|
-
def self.associated_ids_for(backend, klass, name, inversed, *these_ids)
|
92
|
-
these_ids.each_with_object({}) do |this_id, memo|
|
93
|
-
key = Zermelo::Records::Key.new(
|
94
|
-
:klass => klass,
|
95
|
-
:id => this_id,
|
96
|
-
:name => 'belongs_to',
|
97
|
-
:type => :hash,
|
98
|
-
:object => :association
|
99
|
-
)
|
100
|
-
|
101
|
-
assoc_id = backend.get(key)["#{name}_id"]
|
102
|
-
# assoc_id = backend.get_hash_value(key, "#{name}_id")
|
103
|
-
|
104
|
-
if inversed
|
105
|
-
memo[assoc_id] ||= []
|
106
|
-
memo[assoc_id] << this_id
|
107
|
-
else
|
108
|
-
memo[this_id] = assoc_id
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
@@ -1,128 +0,0 @@
|
|
1
|
-
require 'forwardable'
|
2
|
-
|
3
|
-
# much like a has_many, but with different add/remove behaviour, as it's paired
|
4
|
-
# with another has_and_belongs_to_many association. both sides must set the
|
5
|
-
# inverse association name.
|
6
|
-
|
7
|
-
module Zermelo
|
8
|
-
module Associations
|
9
|
-
class HasAndBelongsToMany
|
10
|
-
|
11
|
-
extend Forwardable
|
12
|
-
|
13
|
-
def_delegators :filter, :intersect, :union, :diff, :sort,
|
14
|
-
:find_by_id, :find_by_ids, :find_by_id!, :find_by_ids!,
|
15
|
-
:page, :all, :each, :collect, :map,
|
16
|
-
:select, :find_all, :reject, :destroy_all,
|
17
|
-
:ids, :count, :empty?, :exists?,
|
18
|
-
:associated_ids_for
|
19
|
-
|
20
|
-
def initialize(parent, name)
|
21
|
-
@parent = parent
|
22
|
-
|
23
|
-
@backend = parent.send(:backend)
|
24
|
-
|
25
|
-
@record_ids_key = Zermelo::Records::Key.new(
|
26
|
-
:klass => parent.class,
|
27
|
-
:id => parent.id,
|
28
|
-
:name => "#{name}_ids",
|
29
|
-
:type => :set,
|
30
|
-
:object => :association
|
31
|
-
)
|
32
|
-
|
33
|
-
parent.class.send(:with_association_data, name.to_sym) do |data|
|
34
|
-
@associated_class = data.data_klass
|
35
|
-
@lock_klasses = [data.data_klass] + data.related_klasses
|
36
|
-
@inverse = data.inverse
|
37
|
-
@callbacks = data.callbacks
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def <<(record)
|
42
|
-
add(record)
|
43
|
-
self # for << 'a' << 'b'
|
44
|
-
end
|
45
|
-
|
46
|
-
def add(*records)
|
47
|
-
raise 'No records to add' if records.empty?
|
48
|
-
raise 'Invalid record class' unless records.all? {|r| r.is_a?(@associated_class)}
|
49
|
-
raise "Record(s) must have been saved" unless records.all? {|r| r.persisted?}
|
50
|
-
@parent.class.lock(*@lock_klasses) do
|
51
|
-
ba = @callbacks[:before_add]
|
52
|
-
if ba.nil? || !@parent.respond_to?(ba) || !@parent.send(ba, *records).is_a?(FalseClass)
|
53
|
-
records.each do |record|
|
54
|
-
@associated_class.send(:load, record.id).send(@inverse.to_sym).
|
55
|
-
send(:add_without_inverse, @parent)
|
56
|
-
end
|
57
|
-
add_without_inverse(*records)
|
58
|
-
aa = @callbacks[:after_add]
|
59
|
-
@parent.send(aa, *records) if !aa.nil? && @parent.respond_to?(aa)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
# TODO support dependent delete, for now just deletes the association
|
65
|
-
def delete(*records)
|
66
|
-
raise 'No records to delete' if records.empty?
|
67
|
-
raise 'Invalid record class' unless records.all? {|r| r.is_a?(@associated_class)}
|
68
|
-
raise "Record(s) must have been saved" unless records.all? {|r| r.persisted?}
|
69
|
-
@parent.class.lock(*@lock_klasses) do
|
70
|
-
br = @callbacks[:before_remove]
|
71
|
-
if br.nil? || !@parent.respond_to?(br) || !@parent.send(br, *records).is_a?(FalseClass)
|
72
|
-
records.each do |record|
|
73
|
-
@associated_class.send(:load, record.id).send(@inverse.to_sym).
|
74
|
-
send(:delete_without_inverse, @parent)
|
75
|
-
end
|
76
|
-
delete_without_inverse(*records)
|
77
|
-
ar = @callbacks[:after_remove]
|
78
|
-
@parent.send(ar, *records) if !ar.nil? && @parent.respond_to?(ar)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
private
|
84
|
-
|
85
|
-
def add_without_inverse(*records)
|
86
|
-
new_txn = @backend.begin_transaction
|
87
|
-
@backend.add(@record_ids_key, records.map(&:id))
|
88
|
-
@backend.commit_transaction if new_txn
|
89
|
-
end
|
90
|
-
|
91
|
-
def delete_without_inverse(*records)
|
92
|
-
new_txn = @backend.begin_transaction
|
93
|
-
@backend.delete(@record_ids_key, records.map(&:id))
|
94
|
-
@backend.commit_transaction if new_txn
|
95
|
-
end
|
96
|
-
|
97
|
-
# associated will be the other side of the HaBTM; on_remove is always
|
98
|
-
# called inside a lock
|
99
|
-
def on_remove
|
100
|
-
ids.each do |record_id|
|
101
|
-
@associated_class.send(:load, record_id).send(@inverse.to_sym).
|
102
|
-
send(:delete_without_inverse, @parent)
|
103
|
-
end
|
104
|
-
@backend.purge(@record_ids_key)
|
105
|
-
end
|
106
|
-
|
107
|
-
# creates a new filter class each time it's called, to store the
|
108
|
-
# state for this particular filter chain
|
109
|
-
def filter
|
110
|
-
@backend.filter(@record_ids_key, @associated_class)
|
111
|
-
end
|
112
|
-
|
113
|
-
def self.associated_ids_for(backend, klass, name, *these_ids)
|
114
|
-
these_ids.each_with_object({}) do |this_id, memo|
|
115
|
-
key = Zermelo::Records::Key.new(
|
116
|
-
:klass => klass,
|
117
|
-
:id => this_id,
|
118
|
-
:name => "#{name}_ids",
|
119
|
-
:type => :set,
|
120
|
-
:object => :association
|
121
|
-
)
|
122
|
-
memo[this_id] = backend.get(key)
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|