modis 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,179 @@
1
+ require 'spec_helper'
2
+
3
+ module AttributesSpec
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
+ attribute :hash_strict, :hash, strict: true
15
+ attribute :hash_not_strict, :hash, strict: false
16
+ end
17
+ end
18
+
19
+ describe Modis::Attributes do
20
+ let(:model) { AttributesSpec::MockModel.new }
21
+
22
+ it 'defines attributes' do
23
+ model.name = 'bar'
24
+ model.name.should == 'bar'
25
+ end
26
+
27
+ it 'applies an default value' do
28
+ model.name.should eq 'Janet'
29
+ model.age.should eq 60
30
+ end
31
+
32
+ it 'does not mark an attribute with a default as dirty' do
33
+ model.name_changed?.should be_false
34
+ end
35
+
36
+ it 'raises an error for an unsupported attribute type' do
37
+ expect do
38
+ class AttributesSpec::MockModel
39
+ attribute :unsupported, :symbol
40
+ end
41
+ end.to raise_error(Modis::UnsupportedAttributeType)
42
+ end
43
+
44
+ it 'assigns attributes' do
45
+ model.assign_attributes(name: 'bar')
46
+ model.name.should 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
+ model.respond_to?(:missing_attr).should be_false
52
+ end
53
+
54
+ it 'allows an attribute to be nilled' do
55
+ model.name = nil
56
+ model.save!
57
+ model.class.find(model.id).name.should be_nil
58
+ end
59
+
60
+ describe ':string type' do
61
+ it 'is coerced' do
62
+ model.name = 'Ian'
63
+ model.save!
64
+ found = AttributesSpec::MockModel.find(model.id)
65
+ found.name.should eq 'Ian'
66
+ end
67
+ end
68
+
69
+ describe ':integer type' do
70
+ it 'is coerced' do
71
+ model.age = 18
72
+ model.save!
73
+ found = AttributesSpec::MockModel.find(model.id)
74
+ found.age.should eq 18
75
+ end
76
+ end
77
+
78
+ describe ':float type' do
79
+ it 'is coerced' do
80
+ model.percentage = 18.6
81
+ model.save!
82
+ found = AttributesSpec::MockModel.find(model.id)
83
+ found.percentage.should eq 18.6
84
+ end
85
+
86
+ it 'coerces a string representation to Float' do
87
+ model.percentage = '18.6'
88
+ model.save!
89
+ found = AttributesSpec::MockModel.find(model.id)
90
+ found.percentage.should eq 18.6
91
+ end
92
+ end
93
+
94
+ describe ':timestamp type' do
95
+ it 'is coerced' do
96
+ now = Time.now
97
+ model.created_at = now
98
+ model.save!
99
+ found = AttributesSpec::MockModel.find(model.id)
100
+ found.created_at.should be_kind_of(Time)
101
+ found.created_at.to_s.should eq now.to_s
102
+ end
103
+
104
+ it 'coerces a string representation to Time' do
105
+ now = Time.now
106
+ model.created_at = now.to_s
107
+ model.save!
108
+ found = AttributesSpec::MockModel.find(model.id)
109
+ found.created_at.should be_kind_of(Time)
110
+ found.created_at.to_s.should eq now.to_s
111
+ end
112
+ end
113
+
114
+ describe ':boolean type' do
115
+ it 'is coerced' do
116
+ model.flag = 'true'
117
+ model.save!
118
+ found = AttributesSpec::MockModel.find(model.id)
119
+ found.flag.should eq true
120
+ end
121
+
122
+ it 'raises an error if assigned a non-boolean value' do
123
+ expect { model.flag = 'unf!' }.to raise_error(Modis::AttributeCoercionError)
124
+ end
125
+ end
126
+
127
+ describe ':array type' do
128
+ it 'is coerced' do
129
+ model.array = [1, 2, 3]
130
+ model.save!
131
+ found = AttributesSpec::MockModel.find(model.id)
132
+ found.array.should eq [1, 2, 3]
133
+ end
134
+
135
+ it 'raises an error when assigned another type' do
136
+ expect { model.array = {foo: :bar} }.to raise_error(Modis::AttributeCoercionError)
137
+ end
138
+
139
+ it 'does not raise an error when assigned a JSON array string' do
140
+ expect { model.array = "[1,2,3]" }.to_not raise_error
141
+ end
142
+
143
+ it 'does not raise an error when a JSON string does not deserialize to an Array' do
144
+ expect { model.array = "{\"foo\":\"bar\"}" }.to raise_error(Modis::AttributeCoercionError)
145
+ end
146
+ end
147
+
148
+ describe ':hash type' do
149
+ it 'is coerced' do
150
+ model.hash = {foo: :bar}
151
+ model.save!
152
+ found = AttributesSpec::MockModel.find(model.id)
153
+ found.hash.should eq({'foo' => 'bar'})
154
+ end
155
+
156
+ describe 'strict: true' do
157
+ it 'raises an error if the value cannot be decoded on assignment' do
158
+ expect { model.hash_strict = "test" }.to raise_error(MultiJson::ParseError)
159
+ end
160
+
161
+ it 'does not raise an error if the value can be decoded on assignment' do
162
+ expect { model.hash_strict = "{\"foo\":\"bar\"}" }.to_not raise_error
163
+ end
164
+ end
165
+
166
+ describe 'strict: false' do
167
+ it 'returns the value if it cannot be decoded' do
168
+ model.write_attribute(:hash_not_strict, "test")
169
+ model.save!
170
+ found = AttributesSpec::MockModel.find(model.id)
171
+ found.hash_not_strict.should eq "test"
172
+ end
173
+
174
+ it 'does not raise an error when assigned another type' do
175
+ expect { model.hash_not_strict = "test" }.to_not raise_error
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ module ErrorsSpec
4
+ class MockModel
5
+ include Modis::Model
6
+
7
+ attribute :name, :string
8
+ end
9
+ end
10
+
11
+ describe Modis::Errors do
12
+ let(:model) { ErrorsSpec::MockModel.new }
13
+
14
+ it 'adds errors' do
15
+ model.errors.add(:name, 'is not valid')
16
+ model.errors[:name].should eq ['is not valid']
17
+ end
18
+ end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ module FindersSpec
4
+ class User
5
+ include Modis::Model
6
+ self.namespace = 'users'
7
+
8
+ attribute :name, :string
9
+ attribute :age, :integer
10
+ end
11
+
12
+ class Consumer < User
13
+ end
14
+
15
+ class Producer < User
16
+ end
17
+ end
18
+
19
+ describe Modis::Finders do
20
+ let!(:model) { FindersSpec::User.create!(name: 'Ian', age: 28) }
21
+ let(:found) { FindersSpec::User.find(model.id) }
22
+
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
27
+ end
28
+
29
+ it 'raises an error if the record could not be found' do
30
+ expect do
31
+ FindersSpec::User.find(model.id + 1)
32
+ end.to raise_error(Modis::RecordNotFound, "Couldn't find FindersSpec::User with id=#{model.id + 1}")
33
+ end
34
+
35
+ it 'does not flag an attribute as dirty on a found instance' do
36
+ found.id_changed?.should be_false
37
+ end
38
+
39
+ describe 'all' do
40
+ it 'returns all records' do
41
+ m2 = FindersSpec::User.create!(name: 'Tanya', age: 30)
42
+ m3 = FindersSpec::User.create!(name: 'Kyle', age: 32)
43
+
44
+ FindersSpec::User.all.should == [model, m2, m3]
45
+ end
46
+
47
+ it 'does not return a destroyed record' do
48
+ model.destroy
49
+ FindersSpec::User.all.should == []
50
+ end
51
+ end
52
+
53
+ it 'identifies a found record as not being new' do
54
+ found.new_record?.should be_false
55
+ end
56
+
57
+ describe 'Single Table Inheritance' do
58
+ 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'
63
+ end
64
+
65
+ it 'returns instances of the correct class' do
66
+ FindersSpec::Consumer.create!(name: 'Kyle')
67
+ FindersSpec::Producer.create!(name: 'Tanya')
68
+
69
+ models = FindersSpec::User.all
70
+
71
+ ian = models.find { |model| model.name == 'Ian' }
72
+ kyle = models.find { |model| model.name == 'Kyle' }
73
+ tanya = models.find { |model| model.name == 'Tanya' }
74
+
75
+ ian.should be_kind_of(FindersSpec::User)
76
+ kyle.should be_kind_of(FindersSpec::Consumer)
77
+ tanya.should be_kind_of(FindersSpec::Producer)
78
+
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)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,266 @@
1
+ require 'spec_helper'
2
+
3
+ module PersistenceSpec
4
+ class MockModel
5
+ include Modis::Model
6
+
7
+ attribute :name, :string, default: 'Ian'
8
+ attribute :age, :integer
9
+ validates :name, presence: true
10
+
11
+ before_create :test_before_create
12
+ after_create :test_after_create
13
+
14
+ before_update :test_before_update
15
+ after_update :test_after_update
16
+
17
+ before_save :test_before_save
18
+ after_save :test_after_save
19
+
20
+ def called_callbacks
21
+ @called_callbacks ||= []
22
+ end
23
+
24
+ def test_after_create
25
+ called_callbacks << :test_after_create
26
+ end
27
+
28
+ def test_before_create
29
+ called_callbacks << :test_before_create
30
+ end
31
+
32
+ def test_after_update
33
+ called_callbacks << :test_after_update
34
+ end
35
+
36
+ def test_before_update
37
+ called_callbacks << :test_before_update
38
+ end
39
+
40
+ def test_after_save
41
+ called_callbacks << :test_after_save
42
+ end
43
+
44
+ def test_before_save
45
+ called_callbacks << :test_before_save
46
+ end
47
+ end
48
+ end
49
+
50
+ describe Modis::Persistence do
51
+ let(:model) { PersistenceSpec::MockModel.new }
52
+
53
+ describe 'namespaces' do
54
+ it 'returns the namespace' do
55
+ PersistenceSpec::MockModel.namespace.should eq 'persistence_spec:mock_model'
56
+ end
57
+
58
+ it 'returns the absolute namespace' do
59
+ PersistenceSpec::MockModel.absolute_namespace.should eq 'modis:persistence_spec:mock_model'
60
+ end
61
+
62
+ it 'allows the namespace to be set explicitly' do
63
+ PersistenceSpec::MockModel.namespace = 'other'
64
+ PersistenceSpec::MockModel.absolute_namespace.should eq 'modis:other'
65
+ end
66
+
67
+ after { PersistenceSpec::MockModel.namespace = nil }
68
+ end
69
+
70
+ it 'returns a key' do
71
+ model.save!
72
+ model.key.should eq 'modis:persistence_spec:mock_model:1'
73
+ end
74
+
75
+ it 'returns a nil key if not saved' do
76
+ model.key.should be_nil
77
+ end
78
+
79
+ it 'works with ActiveModel dirty tracking' do
80
+ expect { model.name = 'Kyle' }.to change(model, :changed).to(['name'])
81
+ model.name_changed?.should be_true
82
+ end
83
+
84
+ it 'resets dirty tracking when saved' do
85
+ model.name = 'Kyle'
86
+ model.name_changed?.should be_true
87
+ model.save!
88
+ model.name_changed?.should be_false
89
+ end
90
+
91
+ it 'resets dirty tracking when created' do
92
+ model = PersistenceSpec::MockModel.create!(name: 'Ian')
93
+ model.name_changed?.should be_false
94
+ end
95
+
96
+ it 'is persisted' do
97
+ model.persisted?.should be_true
98
+ end
99
+
100
+ it 'does not track the ID if the underlying Redis command failed'
101
+
102
+ it 'does not perform validation if validate: false' do
103
+ model.name = nil
104
+ model.valid?.should be_false
105
+ expect { model.save!(validate: false) }.to_not raise_error
106
+ model.reload
107
+ model.name.should be_nil
108
+
109
+ model.save(validate: false).should be_true
110
+ end
111
+
112
+ describe 'an existing record' do
113
+ it 'only updates dirty attributes'
114
+ end
115
+
116
+ describe 'reload' do
117
+ it 'reloads attributes' do
118
+ model.save!
119
+ model2 = model.class.find(model.id)
120
+ model2.name = 'Changed'
121
+ model2.save!
122
+ expect { model.reload }.to change(model, :name).to('Changed')
123
+ end
124
+
125
+ it 'resets dirty tracking' do
126
+ model.save!
127
+ model.name = 'Foo'
128
+ model.name_changed?.should be_true
129
+ model.reload
130
+ model.name_changed?.should be_false
131
+ end
132
+
133
+ it 'raises an error if the record has not been saved' do
134
+ expect { model.reload }.to raise_error(Modis::RecordNotFound, "Couldn't find PersistenceSpec::MockModel without an ID")
135
+ end
136
+ end
137
+
138
+ describe 'callbacks' do
139
+ it 'preserves dirty state for the duration of the callback life cycle'
140
+ it 'halts the chain if a callback returns false'
141
+
142
+ describe 'a new record' do
143
+ it 'calls the before_create callback' do
144
+ model.save!
145
+ model.called_callbacks.should include(:test_before_create)
146
+ end
147
+
148
+ it 'calls the after create callback' do
149
+ model.save!
150
+ model.called_callbacks.should include(:test_after_create)
151
+ end
152
+ end
153
+
154
+ describe 'an existing record' do
155
+ before { model.save! }
156
+
157
+ it 'calls the before_update callback' do
158
+ model.save!
159
+ model.called_callbacks.should include(:test_before_update)
160
+ end
161
+
162
+ it 'calls the after update callback' do
163
+ model.save!
164
+ model.called_callbacks.should include(:test_after_update)
165
+ end
166
+ end
167
+
168
+ it 'calls the before_save callback' do
169
+ model.save!
170
+ model.called_callbacks.should include(:test_before_save)
171
+ end
172
+
173
+ it 'calls the after save callback' do
174
+ model.save!
175
+ model.called_callbacks.should include(:test_after_save)
176
+ end
177
+ end
178
+
179
+ describe 'create' do
180
+ it 'resets dirty tracking' do
181
+ model = PersistenceSpec::MockModel.create(name: 'Ian')
182
+ model.name_changed?.should be_false
183
+ end
184
+
185
+ describe 'a valid model' do
186
+ it 'returns the created model'
187
+ end
188
+
189
+ describe 'an invalid model' do
190
+ it 'returns the unsaved model'
191
+ end
192
+ end
193
+
194
+ describe 'update_attribute' do
195
+ it 'does not perform validation' do
196
+ model.name = nil
197
+ model.valid?.should be_false
198
+ model.name = 'Test'
199
+ model.update_attribute(:name, nil)
200
+ end
201
+
202
+ it 'invokes callbacks' do
203
+ model.update_attribute(:name, 'Derp')
204
+ model.called_callbacks.should_not be_empty
205
+ end
206
+
207
+ it 'updates all dirty attributes' do
208
+ model.age = 29
209
+ model.update_attribute(:name, 'Derp')
210
+ model.reload
211
+ model.age.should eq 29
212
+ end
213
+ end
214
+
215
+ describe 'update_attributes!' do
216
+ it 'updates the given attributes' do
217
+ model.update_attributes!(name: 'Derp', age: 29)
218
+ model.reload
219
+ model.name.should eq 'Derp'
220
+ model.age.should eq 29
221
+ end
222
+
223
+ it 'invokes callbacks' do
224
+ model.update_attributes!(name: 'Derp')
225
+ model.called_callbacks.should_not be_empty
226
+ end
227
+
228
+ it 'updates all dirty attributes' do
229
+ model.age = 29
230
+ model.update_attributes!(name: 'Derp')
231
+ model.reload
232
+ model.age.should eq 29
233
+ end
234
+
235
+ it 'raises an error if the model is invalid' do
236
+ expect do
237
+ model.update_attributes!(name: nil).should be_false
238
+ end.to raise_error(Modis::RecordInvalid)
239
+ end
240
+ end
241
+
242
+ describe 'update_attributes' do
243
+ it 'updates the given attributes' do
244
+ model.update_attributes(name: 'Derp', age: 29)
245
+ model.reload
246
+ model.name.should eq 'Derp'
247
+ model.age.should eq 29
248
+ end
249
+
250
+ it 'invokes callbacks' do
251
+ model.update_attributes(name: 'Derp')
252
+ model.called_callbacks.should_not be_empty
253
+ end
254
+
255
+ it 'updates all dirty attributes' do
256
+ model.age = 29
257
+ model.update_attributes(name: 'Derp')
258
+ model.reload
259
+ model.age.should eq 29
260
+ end
261
+
262
+ it 'returns false if the model is invalid' do
263
+ model.update_attributes(name: nil).should be_false
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,20 @@
1
+ begin
2
+ require './spec/support/simplecov_helper'
3
+ include SimpleCovHelper
4
+ start_simple_cov('unit')
5
+ rescue LoadError
6
+ puts "Coverage disabled."
7
+ end
8
+
9
+ require 'modis'
10
+
11
+ Modis.configure do |config|
12
+ config.namespace = 'modis'
13
+ end
14
+
15
+ RSpec.configure do |config|
16
+ config.after :each do
17
+ keys = Modis.redis.keys "#{Modis.config.namespace}:*"
18
+ Modis.redis.del *keys unless keys.empty?
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ require 'simplecov'
2
+ require './spec/support/simplecov_quality_formatter'
3
+
4
+ module SimpleCovHelper
5
+ def start_simple_cov(name)
6
+ SimpleCov.start do
7
+ add_filter '/spec/'
8
+ command_name name
9
+
10
+ if ENV['TRAVIS']
11
+ require 'coveralls'
12
+ formatter SimpleCov::Formatter::MultiFormatter[SimpleCov::Formatter::QualityFormatter,
13
+ Coveralls::SimpleCov::Formatter]
14
+ else
15
+ formatter SimpleCov::Formatter::QualityFormatter
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,8 @@
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
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ module TransactionSpec
4
+ class MockModel
5
+ include Modis::Model
6
+ end
7
+ end
8
+
9
+ describe Modis::Transaction do
10
+ it 'yields the block in a transaction' do
11
+ Modis.redis.should_receive(:multi).and_yield
12
+ TransactionSpec::MockModel.transaction {}
13
+ end
14
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'validations' do
4
+ class TestModel
5
+ include Modis::Model
6
+ attribute :name, :string
7
+ validates :name, presence: true
8
+ end
9
+
10
+ let(:model) { TestModel.new }
11
+
12
+ it 'responds to valid?' do
13
+ model.name = nil
14
+ model.valid?.should be_false
15
+ end
16
+
17
+ it 'sets errors on the model' do
18
+ model.name = nil
19
+ model.valid?
20
+ model.errors[:name].should eq ["can't be blank"]
21
+ end
22
+
23
+ describe 'save' do
24
+ it 'returns true if the model is valid' do
25
+ model.name = "Ian"
26
+ model.save.should be_true
27
+ end
28
+
29
+ it 'returns false if the model is invalid' do
30
+ model.name = nil
31
+ model.save.should be_false
32
+ end
33
+ end
34
+
35
+ describe 'save!' do
36
+ it 'raises an error if the model is invalid' do
37
+ model.name = nil
38
+ expect do
39
+ model.save!.should be_false
40
+ end.to raise_error(Modis::RecordInvalid)
41
+ end
42
+ end
43
+
44
+ describe 'create!' do
45
+ it 'raises an error if the record is not valid' do
46
+ expect { TestModel.create!(name: nil) }.to raise_error(Modis::RecordInvalid)
47
+ end
48
+ end
49
+ end