zermelo 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|