modis 0.0.1

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.
@@ -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