modis 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/modis.rb CHANGED
@@ -2,15 +2,16 @@ require 'redis'
2
2
  require 'connection_pool'
3
3
  require 'active_model'
4
4
  require 'active_support/all'
5
- require 'multi_json'
5
+ require 'yaml'
6
6
 
7
7
  require 'modis/version'
8
8
  require 'modis/configuration'
9
- require 'modis/attributes'
9
+ require 'modis/attribute'
10
10
  require 'modis/errors'
11
11
  require 'modis/persistence'
12
12
  require 'modis/transaction'
13
- require 'modis/finders'
13
+ require 'modis/finder'
14
+ require 'modis/index'
14
15
  require 'modis/model'
15
16
 
16
17
  module Modis
@@ -18,7 +19,7 @@ module Modis
18
19
 
19
20
  class << self
20
21
  attr_accessor :connection_pool, :redis_options, :connection_pool_size,
21
- :connection_pool_timeout
22
+ :connection_pool_timeout
22
23
  end
23
24
 
24
25
  self.redis_options = {}
@@ -0,0 +1,34 @@
1
+ begin
2
+ require 'cane/rake_task'
3
+
4
+ desc 'Run cane to check quality metrics'
5
+ Cane::RakeTask.new(:cane_quality) do |cane|
6
+ cane.add_threshold 'coverage/covered_percent', :>=, 99
7
+ cane.no_style = false
8
+ cane.style_measure = 1000
9
+ cane.no_doc = true
10
+ cane.abc_max = 25
11
+ end
12
+
13
+ namespace :spec do
14
+ task cane: %w(spec cane_quality)
15
+ end
16
+ rescue LoadError
17
+ warn "cane not available."
18
+
19
+ namespace :spec do
20
+ task cane: ['spec']
21
+ end
22
+ end
23
+
24
+ begin
25
+ require 'rubocop/rake_task'
26
+ RuboCop::RakeTask.new
27
+ rescue LoadError
28
+ warn 'rubocop not available.'
29
+ task rubocop: ['spec']
30
+ end
31
+
32
+ namespace :spec do
33
+ task quality: %w(cane rubocop)
34
+ end
@@ -0,0 +1,153 @@
1
+ require 'spec_helper'
2
+
3
+ module AttributeSpec
4
+ class MockModel
5
+ include Modis::Model
6
+
7
+ attribute :name, :string, default: 'Janet'
8
+ attribute :age, :integer, default: 60
9
+ attribute :percentage, :float
10
+ attribute :created_at, :timestamp
11
+ attribute :flag, :boolean
12
+ attribute :array, :array
13
+ attribute :hash, :hash
14
+ end
15
+ end
16
+
17
+ describe Modis::Attribute do
18
+ let(:model) { AttributeSpec::MockModel.new }
19
+
20
+ it 'defines attributes' do
21
+ model.name = 'bar'
22
+ expect(model.name).to eq('bar')
23
+ end
24
+
25
+ it 'applies an default value' do
26
+ expect(model.name).to eq('Janet')
27
+ expect(model.age).to eq(60)
28
+ end
29
+
30
+ it 'does not mark an attribute with a default as dirty' do
31
+ expect(model.name_changed?).to be false
32
+ end
33
+
34
+ it 'raises an error for an unsupported attribute type' do
35
+ expect do
36
+ module AttributeSpec
37
+ class MockModel
38
+ attribute :unsupported, :symbol
39
+ end
40
+ end.to raise_error(Modis::UnsupportedAttributeType)
41
+ end
42
+ end
43
+
44
+ it 'assigns attributes' do
45
+ model.assign_attributes(name: 'bar')
46
+ expect(model.name).to eq 'bar'
47
+ end
48
+
49
+ it 'does not attempt to assign attributes that are not defined on the model' do
50
+ model.assign_attributes(missing_attr: 'derp')
51
+ expect(model.respond_to?(:missing_attrexpect)).to be false
52
+ end
53
+
54
+ it 'allows an attribute to be nilled' do
55
+ model.name = nil
56
+ model.save!
57
+ expect(model.class.find(model.id).name).to be_nil
58
+ end
59
+
60
+ it 'allows an attribute to be a blank string' do
61
+ model.name = ''
62
+ model.save!
63
+ expect(model.class.find(model.id).name).to eq('')
64
+ end
65
+
66
+ describe ':string type' do
67
+ it 'is coerced' do
68
+ model.name = 'Ian'
69
+ model.save!
70
+ found = AttributeSpec::MockModel.find(model.id)
71
+ expect(found.name).to eq('Ian')
72
+ end
73
+ end
74
+
75
+ describe ':integer type' do
76
+ it 'is coerced' do
77
+ model.age = 18
78
+ model.save!
79
+ found = AttributeSpec::MockModel.find(model.id)
80
+ expect(found.age).to eq(18)
81
+ end
82
+ end
83
+
84
+ describe ':float type' do
85
+ it 'is coerced' do
86
+ model.percentage = 18.6
87
+ model.save!
88
+ found = AttributeSpec::MockModel.find(model.id)
89
+ expect(found.percentage).to eq(18.6)
90
+ end
91
+ end
92
+
93
+ describe ':timestamp type' do
94
+ it 'is coerced' do
95
+ now = Time.now
96
+ model.created_at = now
97
+ model.save!
98
+ found = AttributeSpec::MockModel.find(model.id)
99
+ expect(found.created_at).to be_kind_of(Time)
100
+ expect(found.created_at.to_s).to eq(now.to_s)
101
+ end
102
+ end
103
+
104
+ describe ':boolean type' do
105
+ describe true do
106
+ it 'is coerced' do
107
+ model.flag = true
108
+ model.save!
109
+ found = AttributeSpec::MockModel.find(model.id)
110
+ expect(found.flag).to be true
111
+ end
112
+ end
113
+
114
+ describe false do
115
+ it 'is coerced' do
116
+ model.flag = false
117
+ model.save!
118
+ found = AttributeSpec::MockModel.find(model.id)
119
+ expect(found.flag).to be false
120
+ end
121
+ end
122
+
123
+ it 'raises an error if assigned a non-boolean value' do
124
+ expect { model.flag = 'unf!' }.to raise_error(Modis::AttributeCoercionError)
125
+ end
126
+ end
127
+
128
+ describe ':array type' do
129
+ it 'is coerced' do
130
+ model.array = [1, 2, 3]
131
+ model.save!
132
+ found = AttributeSpec::MockModel.find(model.id)
133
+ expect(found.array).to eq([1, 2, 3])
134
+ end
135
+
136
+ it 'raises an error when assigned another type' do
137
+ expect { model.array = { foo: :bar } }.to raise_error(Modis::AttributeCoercionError)
138
+ end
139
+ end
140
+
141
+ describe ':hash type' do
142
+ it 'is coerced' do
143
+ model.hash = { foo: :bar }
144
+ model.save!
145
+ found = AttributeSpec::MockModel.find(model.id)
146
+ expect(found.hash).to eq(foo: :bar)
147
+ end
148
+
149
+ it 'raises an error when assigned another type' do
150
+ expect { model.hash = [] }.to raise_error(Modis::AttributeCoercionError)
151
+ end
152
+ end
153
+ end
data/spec/errors_spec.rb CHANGED
@@ -13,6 +13,6 @@ describe Modis::Errors do
13
13
 
14
14
  it 'adds errors' do
15
15
  model.errors.add(:name, 'is not valid')
16
- model.errors[:name].should eq ['is not valid']
16
+ expect(model.errors[:name]).to eq(['is not valid'])
17
17
  end
18
18
  end
@@ -16,14 +16,14 @@ module FindersSpec
16
16
  end
17
17
  end
18
18
 
19
- describe Modis::Finders do
19
+ describe Modis::Finder do
20
20
  let!(:model) { FindersSpec::User.create!(name: 'Ian', age: 28) }
21
21
  let(:found) { FindersSpec::User.find(model.id) }
22
22
 
23
23
  it 'finds by ID' do
24
- found.id.should eq model.id
25
- found.name.should eq model.name
26
- found.age.should eq model.age
24
+ expect(found.id).to eq(model.id)
25
+ expect(found.name).to eq(model.name)
26
+ expect(found.age).to eq(model.age)
27
27
  end
28
28
 
29
29
  it 'raises an error if the record could not be found' do
@@ -33,33 +33,32 @@ describe Modis::Finders do
33
33
  end
34
34
 
35
35
  it 'does not flag an attribute as dirty on a found instance' do
36
- found.id_changed?.should be_false
36
+ expect(found.id_changed?).to be false
37
37
  end
38
38
 
39
39
  describe 'all' do
40
40
  it 'returns all records' do
41
41
  m2 = FindersSpec::User.create!(name: 'Tanya', age: 30)
42
42
  m3 = FindersSpec::User.create!(name: 'Kyle', age: 32)
43
-
44
- FindersSpec::User.all.should == [model, m2, m3]
43
+ expect(FindersSpec::User.all).to eq([model, m2, m3])
45
44
  end
46
45
 
47
46
  it 'does not return a destroyed record' do
48
47
  model.destroy
49
- FindersSpec::User.all.should == []
48
+ expect(FindersSpec::User.all).to eq([])
50
49
  end
51
50
  end
52
51
 
53
52
  it 'identifies a found record as not being new' do
54
- found.new_record?.should be_false
53
+ expect(found.new_record?).to be false
55
54
  end
56
55
 
57
56
  describe 'Single Table Inheritance' do
58
57
  it 'returns the correct namespace' do
59
- FindersSpec::Consumer.namespace.should eq 'users'
60
- FindersSpec::Consumer.absolute_namespace.should eq 'modis:users'
61
- FindersSpec::Producer.namespace.should eq 'users'
62
- FindersSpec::Producer.absolute_namespace.should eq 'modis:users'
58
+ expect(FindersSpec::Consumer.namespace).to eq('users')
59
+ expect(FindersSpec::Consumer.absolute_namespace).to eq('modis:users')
60
+ expect(FindersSpec::Producer.namespace).to eq('users')
61
+ expect(FindersSpec::Producer.absolute_namespace).to eq('modis:users')
63
62
  end
64
63
 
65
64
  it 'returns instances of the correct class' do
@@ -72,13 +71,13 @@ describe Modis::Finders do
72
71
  kyle = models.find { |model| model.name == 'Kyle' }
73
72
  tanya = models.find { |model| model.name == 'Tanya' }
74
73
 
75
- ian.should be_kind_of(FindersSpec::User)
76
- kyle.should be_kind_of(FindersSpec::Consumer)
77
- tanya.should be_kind_of(FindersSpec::Producer)
74
+ expect(ian).to be_kind_of(FindersSpec::User)
75
+ expect(kyle).to be_kind_of(FindersSpec::Consumer)
76
+ expect(tanya).to be_kind_of(FindersSpec::Producer)
78
77
 
79
- FindersSpec::User.find(ian.id).should be_kind_of(FindersSpec::User)
80
- FindersSpec::User.find(kyle.id).should be_kind_of(FindersSpec::Consumer)
81
- FindersSpec::User.find(tanya.id).should be_kind_of(FindersSpec::Producer)
78
+ expect(FindersSpec::User.find(ian.id)).to be_kind_of(FindersSpec::User)
79
+ expect(FindersSpec::User.find(kyle.id)).to be_kind_of(FindersSpec::Consumer)
80
+ expect(FindersSpec::User.find(tanya.id)).to be_kind_of(FindersSpec::Producer)
82
81
  end
83
82
  end
84
83
  end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ module IndexSpec
4
+ class MockModel
5
+ include Modis::Model
6
+
7
+ attribute :name, :string
8
+ index :name
9
+ end
10
+ end
11
+
12
+ describe Modis::Index do
13
+ let!(:model) { IndexSpec::MockModel.create!(name: 'Ian') }
14
+
15
+ describe 'create' do
16
+ it 'adds a new model to the index' do
17
+ index = IndexSpec::MockModel.index_for(:name, 'Ian')
18
+ expect(index).to include(model.id)
19
+ end
20
+ end
21
+
22
+ describe 'update' do
23
+ before do
24
+ model.name = 'Kyle'
25
+ model.save!
26
+ end
27
+
28
+ it 'adds the model to the new index' do
29
+ index = IndexSpec::MockModel.index_for(:name, 'Kyle')
30
+ expect(index).to include(model.id)
31
+ end
32
+
33
+ it 'removes the model from the old index' do
34
+ index = IndexSpec::MockModel.index_for(:name, 'Ian')
35
+ expect(index).to_not include(model.id)
36
+ end
37
+ end
38
+
39
+ describe 'destroy' do
40
+ it 'removes a destroyed model id from the index' do
41
+ model.destroy
42
+ index = IndexSpec::MockModel.index_for(:name, 'Ian')
43
+ expect(index).to_not include(model.id)
44
+ end
45
+
46
+ it 'does not find a destroyed model' do
47
+ model.destroy
48
+ models = IndexSpec::MockModel.where(name: 'Ian')
49
+ expect(models).to be_empty
50
+ end
51
+ end
52
+
53
+ it 'finds by index' do
54
+ models = IndexSpec::MockModel.where(name: 'Ian')
55
+ expect(models.first.name).to eq('Ian')
56
+ end
57
+
58
+ it 'finds multiple matches' do
59
+ IndexSpec::MockModel.create!(name: 'Ian')
60
+ models = IndexSpec::MockModel.where(name: 'Ian')
61
+ expect(models.count).to eq(2)
62
+ end
63
+
64
+ it 'returns an empty array if there are no results' do
65
+ expect(IndexSpec::MockModel.where(name: 'Foo')).to be_empty
66
+ end
67
+
68
+ it 'raises an error when trying to query against multiple indexes' do
69
+ expect { IndexSpec::MockModel.where(name: 'Ian', age: 29) }.to raise_error(Modis::IndexError, 'Queries using multiple indexes is not currently supported.')
70
+ end
71
+
72
+ it 'indexes a nil value' do
73
+ model.name = nil
74
+ model.save!
75
+ expect(IndexSpec::MockModel.where(name: nil)).to include(model)
76
+ end
77
+
78
+ it 'distinguishes between nil and blank string values' do
79
+ model1 = IndexSpec::MockModel.create!(name: nil)
80
+ model2 = IndexSpec::MockModel.create!(name: "")
81
+ expect(IndexSpec::MockModel.where(name: nil)).to eq([model1])
82
+ expect(IndexSpec::MockModel.where(name: "")).to eq([model2])
83
+ end
84
+ end
@@ -52,16 +52,16 @@ describe Modis::Persistence do
52
52
 
53
53
  describe 'namespaces' do
54
54
  it 'returns the namespace' do
55
- PersistenceSpec::MockModel.namespace.should eq 'persistence_spec:mock_model'
55
+ expect(PersistenceSpec::MockModel.namespace).to eq('persistence_spec:mock_model')
56
56
  end
57
57
 
58
58
  it 'returns the absolute namespace' do
59
- PersistenceSpec::MockModel.absolute_namespace.should eq 'modis:persistence_spec:mock_model'
59
+ expect(PersistenceSpec::MockModel.absolute_namespace).to eq('modis:persistence_spec:mock_model')
60
60
  end
61
61
 
62
62
  it 'allows the namespace to be set explicitly' do
63
63
  PersistenceSpec::MockModel.namespace = 'other'
64
- PersistenceSpec::MockModel.absolute_namespace.should eq 'modis:other'
64
+ expect(PersistenceSpec::MockModel.absolute_namespace).to eq('modis:other')
65
65
  end
66
66
 
67
67
  after { PersistenceSpec::MockModel.namespace = nil }
@@ -69,44 +69,44 @@ describe Modis::Persistence do
69
69
 
70
70
  it 'returns a key' do
71
71
  model.save!
72
- model.key.should eq 'modis:persistence_spec:mock_model:1'
72
+ expect(model.key).to eq('modis:persistence_spec:mock_model:1')
73
73
  end
74
74
 
75
75
  it 'returns a nil key if not saved' do
76
- model.key.should be_nil
76
+ expect(model.key).to be_nil
77
77
  end
78
78
 
79
79
  it 'works with ActiveModel dirty tracking' do
80
80
  expect { model.name = 'Kyle' }.to change(model, :changed).to(['name'])
81
- model.name_changed?.should be_true
81
+ expect(model.name_changed?).to be true
82
82
  end
83
83
 
84
84
  it 'resets dirty tracking when saved' do
85
85
  model.name = 'Kyle'
86
- model.name_changed?.should be_true
86
+ expect(model.name_changed?).to be true
87
87
  model.save!
88
- model.name_changed?.should be_false
88
+ expect(model.name_changed?).to be false
89
89
  end
90
90
 
91
91
  it 'resets dirty tracking when created' do
92
92
  model = PersistenceSpec::MockModel.create!(name: 'Ian')
93
- model.name_changed?.should be_false
93
+ expect(model.name_changed?).to be false
94
94
  end
95
95
 
96
96
  it 'is persisted' do
97
- model.persisted?.should be_true
97
+ expect(model.persisted?).to be true
98
98
  end
99
99
 
100
100
  it 'does not track the ID if the underlying Redis command failed'
101
101
 
102
102
  it 'does not perform validation if validate: false' do
103
103
  model.name = nil
104
- model.valid?.should be_false
104
+ expect(model.valid?).to be false
105
105
  expect { model.save!(validate: false) }.to_not raise_error
106
106
  model.reload
107
- model.name.should be_nil
107
+ expect(model.name).to be_nil
108
108
 
109
- model.save(validate: false).should be_true
109
+ expect(model.save(validate: false)).to be true
110
110
  end
111
111
 
112
112
  describe 'an existing record' do
@@ -125,9 +125,9 @@ describe Modis::Persistence do
125
125
  it 'resets dirty tracking' do
126
126
  model.save!
127
127
  model.name = 'Foo'
128
- model.name_changed?.should be_true
128
+ expect(model.name_changed?).to be true
129
129
  model.reload
130
- model.name_changed?.should be_false
130
+ expect(model.name_changed?).to be false
131
131
  end
132
132
 
133
133
  it 'raises an error if the record has not been saved' do
@@ -142,12 +142,12 @@ describe Modis::Persistence do
142
142
  describe 'a new record' do
143
143
  it 'calls the before_create callback' do
144
144
  model.save!
145
- model.called_callbacks.should include(:test_before_create)
145
+ expect(model.called_callbacks).to include(:test_before_create)
146
146
  end
147
147
 
148
148
  it 'calls the after create callback' do
149
149
  model.save!
150
- model.called_callbacks.should include(:test_after_create)
150
+ expect(model.called_callbacks).to include(:test_after_create)
151
151
  end
152
152
  end
153
153
 
@@ -156,30 +156,30 @@ describe Modis::Persistence do
156
156
 
157
157
  it 'calls the before_update callback' do
158
158
  model.save!
159
- model.called_callbacks.should include(:test_before_update)
159
+ expect(model.called_callbacks).to include(:test_before_update)
160
160
  end
161
161
 
162
162
  it 'calls the after update callback' do
163
163
  model.save!
164
- model.called_callbacks.should include(:test_after_update)
164
+ expect(model.called_callbacks).to include(:test_after_update)
165
165
  end
166
166
  end
167
167
 
168
168
  it 'calls the before_save callback' do
169
169
  model.save!
170
- model.called_callbacks.should include(:test_before_save)
170
+ expect(model.called_callbacks).to include(:test_before_save)
171
171
  end
172
172
 
173
173
  it 'calls the after save callback' do
174
174
  model.save!
175
- model.called_callbacks.should include(:test_after_save)
175
+ expect(model.called_callbacks).to include(:test_after_save)
176
176
  end
177
177
  end
178
178
 
179
179
  describe 'create' do
180
180
  it 'resets dirty tracking' do
181
181
  model = PersistenceSpec::MockModel.create(name: 'Ian')
182
- model.name_changed?.should be_false
182
+ expect(model.name_changed?).to be false
183
183
  end
184
184
 
185
185
  describe 'a valid model' do
@@ -194,48 +194,48 @@ describe Modis::Persistence do
194
194
  describe 'update_attribute' do
195
195
  it 'does not perform validation' do
196
196
  model.name = nil
197
- model.valid?.should be_false
197
+ expect(model.valid?).to be false
198
198
  model.name = 'Test'
199
199
  model.update_attribute(:name, nil)
200
200
  end
201
201
 
202
202
  it 'invokes callbacks' do
203
203
  model.update_attribute(:name, 'Derp')
204
- model.called_callbacks.should_not be_empty
204
+ expect(model.called_callbacks).to_not be_empty
205
205
  end
206
206
 
207
207
  it 'updates all dirty attributes' do
208
208
  model.age = 29
209
209
  model.update_attribute(:name, 'Derp')
210
210
  model.reload
211
- model.age.should eq 29
211
+ expect(model.age).to eq 29
212
212
  end
213
213
  end
214
214
 
215
215
  describe 'update_attributes!' do
216
- it 'updates the given attributes' do
216
+ it 'updates the given attributes' do
217
217
  model.update_attributes!(name: 'Derp', age: 29)
218
218
  model.reload
219
- model.name.should eq 'Derp'
220
- model.age.should eq 29
219
+ expect(model.name).to eq 'Derp'
220
+ expect(model.age).to eq 29
221
221
  end
222
222
 
223
223
  it 'invokes callbacks' do
224
224
  model.update_attributes!(name: 'Derp')
225
- model.called_callbacks.should_not be_empty
225
+ expect(model.called_callbacks).to_not be_empty
226
226
  end
227
227
 
228
228
  it 'updates all dirty attributes' do
229
229
  model.age = 29
230
230
  model.update_attributes!(name: 'Derp')
231
231
  model.reload
232
- model.age.should eq 29
232
+ expect(model.age).to eq 29
233
233
  end
234
234
 
235
235
  it 'raises an error if the model is invalid' do
236
236
  expect do
237
- model.update_attributes!(name: nil).should be_false
238
- end.to raise_error(Modis::RecordInvalid)
237
+ model.update_attributes!(name: nil).to be false
238
+ end.to raise_error(Modis::RecordInvalid)
239
239
  end
240
240
  end
241
241
 
@@ -243,24 +243,24 @@ it 'updates the given attributes' do
243
243
  it 'updates the given attributes' do
244
244
  model.update_attributes(name: 'Derp', age: 29)
245
245
  model.reload
246
- model.name.should eq 'Derp'
247
- model.age.should eq 29
246
+ expect(model.name).to eq('Derp')
247
+ expect(model.age).to eq(29)
248
248
  end
249
249
 
250
250
  it 'invokes callbacks' do
251
251
  model.update_attributes(name: 'Derp')
252
- model.called_callbacks.should_not be_empty
252
+ expect(model.called_callbacks).to_not be_empty
253
253
  end
254
254
 
255
255
  it 'updates all dirty attributes' do
256
256
  model.age = 29
257
257
  model.update_attributes(name: 'Derp')
258
258
  model.reload
259
- model.age.should eq 29
259
+ expect(model.age).to eq(29)
260
260
  end
261
261
 
262
262
  it 'returns false if the model is invalid' do
263
- model.update_attributes(name: nil).should be_false
263
+ expect(model.update_attributes(name: nil)).to be false
264
264
  end
265
265
  end
266
266
  end
data/spec/spec_helper.rb CHANGED
@@ -16,7 +16,7 @@ RSpec.configure do |config|
16
16
  config.after :each do
17
17
  Modis.with_connection do |connection|
18
18
  keys = connection.keys "#{Modis.config.namespace}:*"
19
- connection.del *keys unless keys.empty?
19
+ connection.del(*keys) unless keys.empty?
20
20
  end
21
21
  end
22
22
  end
@@ -1,8 +1,12 @@
1
- class SimpleCov::Formatter::QualityFormatter
2
- def format(result)
3
- SimpleCov::Formatter::HTMLFormatter.new.format(result)
4
- File.open("coverage/covered_percent", "w") do |f|
5
- f.puts result.source_files.covered_percent.to_f
1
+ module SimpleCov
2
+ module Formatter
3
+ class QualityFormatter
4
+ def format(result)
5
+ SimpleCov::Formatter::HTMLFormatter.new.format(result)
6
+ File.open("coverage/covered_percent", "w") do |f|
7
+ f.puts result.source_files.covered_percent.to_f
8
+ end
9
+ end
6
10
  end
7
11
  end
8
12
  end
@@ -9,8 +9,8 @@ end
9
9
  describe Modis::Transaction do
10
10
  it 'yields the block in a transaction' do
11
11
  redis = double.as_null_object
12
- Modis.stub(:with_connection).and_yield(redis)
13
- redis.should_receive(:multi)
12
+ allow(Modis).to receive(:with_connection).and_yield(redis)
13
+ expect(redis).to receive(:multi)
14
14
  TransactionSpec::MockModel.transaction {}
15
15
  end
16
16
  end