squares 0.2.9 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/squares/base.rb +186 -29
- data/lib/squares/version.rb +1 -1
- data/spec/squares/base_spec.rb +316 -33
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5881a376cfce79ade243a19dfa8f3a9b112d46e6
|
4
|
+
data.tar.gz: 8530ebccc40ed950e0b1298d524d6e57da969149
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a16c5788667935c396834e0bac628ffee25259472e2a62025d19b38e6838bdfc0e7c047892ecb3e2a90a94f339d66b99dc18c4aa45dce6297b0e4226f1d43600
|
7
|
+
data.tar.gz: a02e43ca8c0141b600c4a2c3840b5ce3d0960b0797226385d4b30f2e7c76d17865436021151bc8aefc53ed4b1a727523be02be857c3d1596ae6a327b4efa7103
|
data/lib/squares/base.rb
CHANGED
@@ -4,17 +4,50 @@ module Squares
|
|
4
4
|
|
5
5
|
def initialize *args
|
6
6
|
apply *args
|
7
|
+
trigger :after_initialize
|
7
8
|
end
|
8
9
|
|
9
10
|
def save
|
10
|
-
|
11
|
+
trigger :before_save
|
12
|
+
@_changed = false
|
13
|
+
store[@id] = Marshal.dump self.dup
|
14
|
+
trigger :after_save
|
11
15
|
nil
|
12
16
|
end
|
13
17
|
|
18
|
+
def delete
|
19
|
+
self.class.delete self.id
|
20
|
+
end
|
21
|
+
|
22
|
+
def destroy
|
23
|
+
trigger :before_destroy
|
24
|
+
delete
|
25
|
+
end
|
26
|
+
|
14
27
|
def == other
|
15
28
|
@id == other.id && properties_equal(other)
|
16
29
|
end
|
17
30
|
|
31
|
+
def [](key)
|
32
|
+
get_property key
|
33
|
+
end
|
34
|
+
|
35
|
+
def []=(key, value)
|
36
|
+
set_property key, value
|
37
|
+
end
|
38
|
+
|
39
|
+
def update_properties new_properties
|
40
|
+
new_properties.each do |key, value|
|
41
|
+
self[key] = value if valid_property?(key)
|
42
|
+
end
|
43
|
+
save
|
44
|
+
end
|
45
|
+
alias_method :update_attributes, :update_properties
|
46
|
+
|
47
|
+
def changed?
|
48
|
+
@_changed
|
49
|
+
end
|
50
|
+
|
18
51
|
def properties
|
19
52
|
self.class.properties
|
20
53
|
end
|
@@ -27,8 +60,28 @@ module Squares
|
|
27
60
|
h
|
28
61
|
end
|
29
62
|
|
63
|
+
def valid_property? property
|
64
|
+
self.class.valid_property? property
|
65
|
+
end
|
66
|
+
|
67
|
+
def defaults
|
68
|
+
self.class.defaults
|
69
|
+
end
|
70
|
+
|
30
71
|
private
|
31
72
|
|
73
|
+
def trigger hook_name
|
74
|
+
return if @hook_callback_in_progress
|
75
|
+
hooks = self.class.hooks
|
76
|
+
if hooks && hooks[hook_name]
|
77
|
+
hooks[hook_name].each do |hook|
|
78
|
+
@hook_callback_in_progress = true
|
79
|
+
self.instance_eval &hook
|
80
|
+
@hook_callback_in_progress = false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
32
85
|
def properties_equal other
|
33
86
|
! properties.detect do |property|
|
34
87
|
self.send(property) != other.send(property)
|
@@ -37,15 +90,47 @@ module Squares
|
|
37
90
|
|
38
91
|
def apply *args
|
39
92
|
@id, values = *args
|
40
|
-
|
41
|
-
|
42
|
-
value =
|
43
|
-
|
93
|
+
values_hash = values.to_h
|
94
|
+
properties_sorted_by_defaults.each do |property|
|
95
|
+
value = values_hash.has_key?(property) ? values_hash[property] : default_for(property)
|
96
|
+
set_property property, value
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def properties_sorted_by_defaults
|
101
|
+
(properties || []).sort do |a,b|
|
102
|
+
a_val = defaults[a].respond_to?(:call) ? 1 : 0
|
103
|
+
b_val = defaults[b].respond_to?(:call) ? 1 : 0
|
104
|
+
a_val <=> b_val
|
44
105
|
end
|
45
106
|
end
|
46
107
|
|
47
108
|
def default_for property
|
48
|
-
|
109
|
+
defaults[property].respond_to?(:call) ?
|
110
|
+
defaults[property].call(self) :
|
111
|
+
defaults[property]
|
112
|
+
end
|
113
|
+
|
114
|
+
def get_property property
|
115
|
+
instance_variable_get "@#{instance_var_string_for property}"
|
116
|
+
end
|
117
|
+
|
118
|
+
def set_property property, value
|
119
|
+
unless valid_property?(property)
|
120
|
+
raise ArgumentError.new("\"#{property}\" is not a valid property of #{self.class}")
|
121
|
+
end
|
122
|
+
instance_variable_set("@#{instance_var_string_for property}", value).tap do |value|
|
123
|
+
@_changed = true
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def instance_var_string_for property
|
128
|
+
property = self.class.normalize_property property
|
129
|
+
if property.to_s.match(/\?$/)
|
130
|
+
"#{property.to_s.gsub(/\?$/,'')}__question__".to_sym
|
131
|
+
else
|
132
|
+
property
|
133
|
+
end
|
49
134
|
end
|
50
135
|
|
51
136
|
def store
|
@@ -58,7 +143,9 @@ module Squares
|
|
58
143
|
|
59
144
|
def [] id
|
60
145
|
if item = store[id]
|
61
|
-
Marshal.restore item
|
146
|
+
Marshal.restore(item).tap do |item|
|
147
|
+
item.instance_eval 'trigger :after_find'
|
148
|
+
end
|
62
149
|
end
|
63
150
|
end
|
64
151
|
alias_method :find, :[]
|
@@ -75,7 +162,11 @@ module Squares
|
|
75
162
|
something which responds to #to_h"
|
76
163
|
ERR
|
77
164
|
end
|
78
|
-
instance.tap
|
165
|
+
instance.tap do |i|
|
166
|
+
i.instance_eval 'trigger :before_create'
|
167
|
+
i.save
|
168
|
+
i.instance_eval 'trigger :after_create'
|
169
|
+
end
|
79
170
|
end
|
80
171
|
alias_method :create, :[]=
|
81
172
|
|
@@ -94,6 +185,17 @@ module Squares
|
|
94
185
|
store.values.map{ |i| Marshal.restore i }
|
95
186
|
end
|
96
187
|
|
188
|
+
def valid_property? property
|
189
|
+
!!normalize_property(property)
|
190
|
+
end
|
191
|
+
|
192
|
+
def normalize_property property
|
193
|
+
unless properties.include?(property)
|
194
|
+
property = "#{property}?".to_sym if properties.include?("#{property}?".to_sym)
|
195
|
+
end
|
196
|
+
properties.include?(property) && property
|
197
|
+
end
|
198
|
+
|
97
199
|
def delete id
|
98
200
|
store.delete id
|
99
201
|
end
|
@@ -101,24 +203,46 @@ module Squares
|
|
101
203
|
def each &block
|
102
204
|
values.each &block
|
103
205
|
end
|
104
|
-
alias_method :where, :select
|
105
206
|
|
106
|
-
def
|
207
|
+
def where(*args, &block)
|
208
|
+
result_set = []
|
209
|
+
if block
|
210
|
+
result_set = values.select(&block)
|
211
|
+
return result_set if result_set.empty?
|
212
|
+
end
|
213
|
+
|
214
|
+
args.each do |arg|
|
215
|
+
if arg.kind_of?(Hash)
|
216
|
+
result_set = (!result_set.empty? ? result_set : values).reject do |i|
|
217
|
+
failed_matches = 0
|
218
|
+
arg.each do |k,v|
|
219
|
+
raise ArgumentError.new("\"#{k}\" is not a valid property of #{self}") unless valid_property?(k)
|
220
|
+
failed_matches += 1 unless i[k] == v
|
221
|
+
end
|
222
|
+
failed_matches > 0
|
223
|
+
end
|
224
|
+
elsif arg.kind_of?(Symbol)
|
225
|
+
raise ArgumentError.new("\"#{arg}\" is not a valid property of #{self}") unless valid_property?(arg)
|
226
|
+
result_set = (result_set.empty? ? values : result_set).select do |i|
|
227
|
+
i[arg]
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
result_set
|
232
|
+
end
|
233
|
+
|
234
|
+
def property prop, opts={}
|
107
235
|
@_properties ||= []
|
108
236
|
@_properties << prop
|
109
237
|
uniquify_properties
|
110
|
-
|
111
|
-
|
112
|
-
instance_variable_get "@#{self.class.instance_var_for prop}"
|
113
|
-
end
|
114
|
-
define_method prop.to_s.gsub(/\?$/, '=') do |v|
|
115
|
-
instance_variable_set "@#{self.class.instance_var_for prop}", v
|
116
|
-
end
|
117
|
-
else
|
118
|
-
attr_accessor prop
|
238
|
+
define_method prop do
|
239
|
+
get_property prop
|
119
240
|
end
|
120
|
-
|
121
|
-
|
241
|
+
define_method "#{prop.to_s.gsub(/\?$/, '')}=" do |v|
|
242
|
+
set_property prop, v
|
243
|
+
end
|
244
|
+
if opts.has_key?(:default)
|
245
|
+
defaults[prop] = opts[:default]
|
122
246
|
end
|
123
247
|
end
|
124
248
|
|
@@ -148,20 +272,53 @@ module Squares
|
|
148
272
|
@store ||= {}
|
149
273
|
end
|
150
274
|
|
151
|
-
def instance_var_for property
|
152
|
-
if property.to_s.match(/\?$/)
|
153
|
-
"#{property.to_s.gsub(/\?$/,'')}__question__".to_sym
|
154
|
-
else
|
155
|
-
property
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
275
|
def models
|
160
276
|
@_models.uniq.sort { |a,b| a.to_s <=> b.to_s }
|
161
277
|
end
|
162
278
|
|
279
|
+
### hooks
|
280
|
+
def hooks
|
281
|
+
@_hooks
|
282
|
+
end
|
283
|
+
|
284
|
+
def before_create *args, &block
|
285
|
+
add_hook :before_create, args, block
|
286
|
+
end
|
287
|
+
|
288
|
+
def after_create *args, &block
|
289
|
+
add_hook :after_create, args, block
|
290
|
+
end
|
291
|
+
|
292
|
+
def after_initialize *args, &block
|
293
|
+
add_hook :after_initialize, args, block
|
294
|
+
end
|
295
|
+
|
296
|
+
def after_find *args, &block
|
297
|
+
add_hook :after_find, args, block
|
298
|
+
end
|
299
|
+
|
300
|
+
def before_save *args, &block
|
301
|
+
add_hook :before_save, args, block
|
302
|
+
end
|
303
|
+
|
304
|
+
def after_save *args, &block
|
305
|
+
add_hook :after_save, args, block
|
306
|
+
end
|
307
|
+
|
308
|
+
def before_destroy *args, &block
|
309
|
+
add_hook :before_destroy, args, block
|
310
|
+
end
|
311
|
+
|
312
|
+
### /hooks
|
313
|
+
|
163
314
|
private
|
164
315
|
|
316
|
+
def add_hook hook_id, args, block
|
317
|
+
@_hooks ||= {}
|
318
|
+
@_hooks[hook_id] ||= []
|
319
|
+
@_hooks[hook_id] << block
|
320
|
+
end
|
321
|
+
|
165
322
|
def uniquify_properties
|
166
323
|
@_properties = @_properties.uniq.compact
|
167
324
|
end
|
data/lib/squares/version.rb
CHANGED
data/spec/squares/base_spec.rb
CHANGED
@@ -4,11 +4,15 @@ require 'squares/base'
|
|
4
4
|
module Marvel
|
5
5
|
class SuperHero < Squares::Base
|
6
6
|
properties :real_name, :special_powers
|
7
|
+
property :caped?, default: false
|
7
8
|
end
|
8
9
|
class Villain < Squares::Base
|
9
10
|
properties :vehicle, :lair
|
10
11
|
property :really_evil?, default: true
|
11
12
|
end
|
13
|
+
class Sidekick < Squares::Base
|
14
|
+
property :catch_phrase
|
15
|
+
end
|
12
16
|
end
|
13
17
|
|
14
18
|
module Squares
|
@@ -22,7 +26,7 @@ module Squares
|
|
22
26
|
Given(:powers) { ['super strength', 'strategy', 'leadership'] }
|
23
27
|
When(:hero) { test_class.new id, real_name: name, special_powers: powers }
|
24
28
|
|
25
|
-
describe 'class' do
|
29
|
+
describe 'class methods' do
|
26
30
|
|
27
31
|
describe '.underscore_name' do
|
28
32
|
Then { test_class.underscore_name == 'marvel/super_hero' }
|
@@ -35,22 +39,22 @@ module Squares
|
|
35
39
|
end
|
36
40
|
|
37
41
|
describe '.properties' do
|
38
|
-
Then { test_class.properties == [:real_name, :special_powers] }
|
42
|
+
Then { test_class.properties == [:real_name, :special_powers, :caped?] }
|
39
43
|
end
|
40
44
|
|
41
|
-
describe '.
|
45
|
+
describe '.storage' do
|
42
46
|
Given(:storage) { {attack: :fwoosh } }
|
43
47
|
When { test_class.store = storage }
|
44
48
|
Then { test_class.store == storage }
|
45
49
|
end
|
46
50
|
|
47
|
-
describe '.models lists defined models (inheritors)' do
|
51
|
+
describe '.models lists defined models (inheritors, sorted alphabetically)' do
|
48
52
|
When(:result) { described_class.models }
|
49
|
-
Then { result == [ Marvel::SuperHero, Marvel::Villain ] }
|
53
|
+
Then { result == [ Marvel::Sidekick, Marvel::SuperHero, Marvel::Villain ] }
|
50
54
|
end
|
51
55
|
|
52
56
|
describe '.[]' do
|
53
|
-
Given
|
57
|
+
Given { storage[id] = Marshal.dump hero }
|
54
58
|
When(:recovered_hero) { Marvel::SuperHero['Captain America'] }
|
55
59
|
Then { recovered_hero.class == Marvel::SuperHero }
|
56
60
|
Then { recovered_hero.id == 'Captain America' }
|
@@ -103,7 +107,7 @@ module Squares
|
|
103
107
|
Then { expect(test_class).to be_includes(id) }
|
104
108
|
end
|
105
109
|
|
106
|
-
describe '
|
110
|
+
describe 'enumerable collections' do
|
107
111
|
Given do
|
108
112
|
Marvel::SuperHero['Superman'] = { real_name: 'Clark Kent', special_powers: ['flying'] }
|
109
113
|
Marvel::SuperHero['Hulk'] = { real_name: 'Bruce Banner', special_powers: ['smash'] }
|
@@ -111,8 +115,48 @@ module Squares
|
|
111
115
|
end
|
112
116
|
|
113
117
|
describe '.where' do
|
114
|
-
|
115
|
-
|
118
|
+
describe 'accepts a block of code (essentailly like .select)' do
|
119
|
+
When(:result) { Marvel::SuperHero.where { |h| h.real_name =~ /^Bruce/ } }
|
120
|
+
Then { result.count == 2 }
|
121
|
+
end
|
122
|
+
|
123
|
+
describe 'accepts a hash' do
|
124
|
+
When(:result) { Marvel::SuperHero.where( real_name: 'Clark Kent' ) }
|
125
|
+
Then { result.count == 1 }
|
126
|
+
end
|
127
|
+
|
128
|
+
describe 'accepts a symbol' do
|
129
|
+
Given { Marvel::SuperHero['The Sponge'] = { special_powers: ['soaking'] } }
|
130
|
+
Then { Marvel::SuperHero.where( :special_powers ).count == 4 }
|
131
|
+
Then { Marvel::SuperHero.where( :real_name ).count == 3 }
|
132
|
+
end
|
133
|
+
|
134
|
+
describe 'accepts boolean symbols' do
|
135
|
+
Given do
|
136
|
+
%w[ Superman Batman ].each do |h|
|
137
|
+
hero = Marvel::SuperHero.find h
|
138
|
+
hero.caped = true
|
139
|
+
hero.save
|
140
|
+
end
|
141
|
+
end
|
142
|
+
Then { Marvel::SuperHero.where(:caped?).count == 2 }
|
143
|
+
Then { Marvel::SuperHero.where(:caped).count == 2 }
|
144
|
+
end
|
145
|
+
|
146
|
+
describe 'accepts both block and args' do
|
147
|
+
Given do
|
148
|
+
Marvel::SuperHero.find('Superman').tap do |superman|
|
149
|
+
superman.special_powers = ['smash']
|
150
|
+
end.save
|
151
|
+
end
|
152
|
+
When(:result) { Marvel::SuperHero.where(special_powers: ['smash']) { |h| h.real_name =~ /^Bruce/ } }
|
153
|
+
Then { result.count == 1 }
|
154
|
+
end
|
155
|
+
|
156
|
+
context 'with invalid properties as arguments' do
|
157
|
+
Given(:bogus_hash) { { bogus: 'hash' } }
|
158
|
+
Then { expect{Marvel::SuperHero.where(bogus_hash)}.to raise_error(ArgumentError) }
|
159
|
+
end
|
116
160
|
end
|
117
161
|
|
118
162
|
describe 'models are enumerable!' do
|
@@ -121,15 +165,29 @@ module Squares
|
|
121
165
|
end
|
122
166
|
end
|
123
167
|
|
124
|
-
end
|
168
|
+
end # class methods
|
125
169
|
|
126
|
-
describe '
|
170
|
+
describe 'instance methods' do
|
127
171
|
describe '#{{property}}' do
|
128
172
|
Then { hero.id == id }
|
129
173
|
Then { hero.real_name == name }
|
130
174
|
Then { hero.special_powers == powers }
|
131
175
|
end
|
132
176
|
|
177
|
+
describe '#{{boolean}}?' do
|
178
|
+
When(:villain) { Marvel::Villain.new 'Lizard Man', really_evil?: false }
|
179
|
+
describe 'has an accessor (ends with "?")' do
|
180
|
+
Then { expect(villain).to respond_to(:really_evil?) }
|
181
|
+
Then { expect(villain).to_not be_really_evil }
|
182
|
+
end
|
183
|
+
|
184
|
+
describe 'also has a setter (ends with "=")' do
|
185
|
+
Then { expect(villain).to respond_to(:really_evil=) }
|
186
|
+
When { villain.really_evil = true }
|
187
|
+
Then { expect(villain).to be_really_evil }
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
133
191
|
describe '#save' do
|
134
192
|
When { hero.save }
|
135
193
|
When(:frozen_hero) { Marshal.restore storage.values.first }
|
@@ -138,16 +196,22 @@ module Squares
|
|
138
196
|
Then { frozen_hero.class == Marvel::SuperHero }
|
139
197
|
end
|
140
198
|
|
199
|
+
describe '#delete' do
|
200
|
+
Given(:villain) { Marvel::Villain.create 'Lizard Man', lair: 'lab' }
|
201
|
+
When { villain.delete }
|
202
|
+
Then { expect(Marvel::Villain[villain.id]).to be_nil }
|
203
|
+
end
|
204
|
+
|
141
205
|
describe '#to_h' do
|
142
206
|
Given(:hero1) { test_class.new id, real_name: name, special_powers: powers }
|
143
207
|
context 'default key name' do
|
144
|
-
Given(:expected_hash) { { id: id, real_name: name, special_powers: powers } }
|
208
|
+
Given(:expected_hash) { { id: id, real_name: name, special_powers: powers, caped?: false } }
|
145
209
|
When(:result) { hero1.to_h }
|
146
210
|
Then { expect(result).to eq(expected_hash) }
|
147
211
|
end
|
148
212
|
|
149
213
|
context 'custom key name' do
|
150
|
-
Given(:expected_hash) { { hero: id, real_name: name, special_powers: powers } }
|
214
|
+
Given(:expected_hash) { { hero: id, real_name: name, special_powers: powers, caped?: false } }
|
151
215
|
When(:result) { hero1.to_h(:hero) }
|
152
216
|
Then { expect(result).to eq(expected_hash) }
|
153
217
|
end
|
@@ -167,34 +231,253 @@ module Squares
|
|
167
231
|
end
|
168
232
|
end
|
169
233
|
|
170
|
-
describe '
|
171
|
-
|
172
|
-
|
173
|
-
|
234
|
+
describe '#[]' do
|
235
|
+
context 'valid key' do
|
236
|
+
Then { expect(hero[:real_name]).to eq(name) }
|
237
|
+
end
|
238
|
+
|
239
|
+
context 'key which is not a property' do
|
240
|
+
Then { expect(hero[:insurance_company]).to be_nil }
|
241
|
+
end
|
242
|
+
|
243
|
+
context 'boolean properties' do
|
244
|
+
When(:villain) { Marvel::Villain.new 'Lizard Man', really_evil?: true }
|
245
|
+
Then { expect(villain[:really_evil?]).to eq(true) }
|
246
|
+
Then { expect(villain[:really_evil]).to eq(true) }
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
describe '#[]=' do
|
251
|
+
context 'valid property' do
|
252
|
+
When { hero[:real_name] = 'Bruce Banner' }
|
253
|
+
Then { expect(hero.real_name).to eq('Bruce Banner') }
|
254
|
+
end
|
255
|
+
|
256
|
+
context 'key which is not a property' do
|
257
|
+
Then { expect { hero[:ip_address] = '127.0.0.1' }.to raise_error(ArgumentError) }
|
258
|
+
end
|
259
|
+
|
260
|
+
context 'boolean properties' do
|
261
|
+
When(:villain) { Marvel::Villain.new 'Lizard Man', really_evil?: false }
|
262
|
+
|
263
|
+
describe 'can be set using the symbol ending in "?"' do
|
264
|
+
When { villain[:really_evil?] = true }
|
265
|
+
Then { expect(villain).to be_really_evil }
|
266
|
+
end
|
267
|
+
|
268
|
+
describe 'can also be set using the variant without the "?"' do
|
269
|
+
When { villain[:really_evil] = true }
|
270
|
+
Then { expect(villain).to be_really_evil }
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
describe '#update_properties' do
|
276
|
+
Given(:villain) { Marvel::Villain.create 'Dr. Doom', vehicle: 'jet', lair: 'Latveria', really_evil?: false }
|
277
|
+
Given(:new_properties) { { vehicle: 'train', hairstyle: 'bald' } }
|
278
|
+
When { villain.update_properties new_properties }
|
279
|
+
|
280
|
+
describe 'un-updated properties stay the same' do
|
281
|
+
Then { expect(villain.lair).to eq('Latveria') }
|
282
|
+
end
|
283
|
+
|
284
|
+
describe 'updated properties are changed' do
|
285
|
+
Then { expect(villain.vehicle).to eq('train') }
|
286
|
+
end
|
287
|
+
|
288
|
+
describe 'invalid properties do nothing' do
|
289
|
+
Then { expect(villain.instance_variable_get("@hairstyle")).to be_nil }
|
290
|
+
end
|
291
|
+
|
292
|
+
describe 'boolean properties' do
|
293
|
+
context 'where key ends in "?"' do
|
294
|
+
Given { new_properties[:really_evil?] = true }
|
295
|
+
Then { expect(villain).to be_really_evil }
|
296
|
+
end
|
297
|
+
context 'where key does not end with "?"' do
|
298
|
+
Given { new_properties[:really_evil] = true }
|
299
|
+
Then { expect(villain).to be_really_evil }
|
174
300
|
end
|
175
301
|
end
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
302
|
+
|
303
|
+
describe 'writes changes to the data store' do
|
304
|
+
Then { expect(Marvel::Villain.find('Dr. Doom').vehicle).to eq('train') }
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
describe '#changed?' do
|
309
|
+
Given(:hero) { test_class.new id, real_name: name, special_powers: powers }
|
310
|
+
context 'new, unsaved' do
|
311
|
+
Then { expect(hero).to be_changed }
|
312
|
+
end
|
313
|
+
context 'after save' do
|
314
|
+
When { hero.save }
|
315
|
+
Then { expect(hero).to_not be_changed }
|
316
|
+
end
|
317
|
+
context 'freshly retrieved from storage' do
|
318
|
+
Given { hero.save }
|
319
|
+
When(:found_hero) { test_class.find id }
|
320
|
+
Then { expect(found_hero).to_not be_changed }
|
321
|
+
end
|
322
|
+
context 'unchanged, but then modified' do
|
323
|
+
Given { hero.save }
|
324
|
+
When { hero.real_name = 'Fred Flintstone' }
|
325
|
+
Then { expect(hero).to be_changed }
|
326
|
+
end
|
327
|
+
context 'after create' do
|
328
|
+
Given(:made_hero) { test_class.create 'Iron Man', real_name: 'Tony Stark', special_powers: ['snark'] }
|
329
|
+
Then { expect(made_hero).to_not be_changed }
|
330
|
+
end
|
180
331
|
end
|
181
332
|
|
333
|
+
describe 'default values' do
|
334
|
+
context 'as a simple value' do
|
335
|
+
Given do
|
336
|
+
class Marvel::SuperHero < Squares::Base
|
337
|
+
property :hair_color, default: 'black'
|
338
|
+
end
|
339
|
+
end
|
340
|
+
When(:hero) { test_class.new id, real_name: name }
|
341
|
+
|
342
|
+
Then { hero.special_powers == nil }
|
343
|
+
Then { hero.hair_color == 'black' }
|
344
|
+
Then { Marvel::SuperHero.defaults == { caped?: false, hair_color: 'black' } }
|
345
|
+
end
|
346
|
+
|
347
|
+
context 'as a callback' do
|
348
|
+
Given do
|
349
|
+
class Marvel::SuperHero < Squares::Base
|
350
|
+
property :hungry?, default: lambda{ |i| i.earthling? && i.body_mass > 150 }
|
351
|
+
property :body_mass, default: 160
|
352
|
+
property :earthling?, default: true
|
353
|
+
end
|
354
|
+
end
|
355
|
+
Given(:the_fly) { test_class.new 'The Fly', body_mass: 110 }
|
356
|
+
Given(:manhunter) { test_class.new 'Manhunter', earthling?: false }
|
357
|
+
Given(:colossus) { test_class.new 'Colossus', body_mass: 350 }
|
358
|
+
Then { expect(the_fly).to_not be_hungry }
|
359
|
+
Then { expect(manhunter).to_not be_hungry }
|
360
|
+
Then { expect(colossus).to be_hungry }
|
361
|
+
end
|
362
|
+
|
363
|
+
end
|
182
364
|
end
|
183
365
|
|
184
|
-
describe
|
366
|
+
describe 'instance properties' do
|
185
367
|
When(:villain) { Marvel::Villain.new 'Dr. Octopus', vehicle: 'jets', lair: 'abandonned sewer' }
|
186
|
-
Then { villain.class == Marvel::Villain }
|
187
|
-
Then { Marvel::Villain.properties == [:vehicle, :lair, :really_evil?] }
|
188
|
-
Then { villain.properties == [:vehicle, :lair, :really_evil?] }
|
189
|
-
Then { expect(villain).to_not respond_to(:hair_color) }
|
190
|
-
Then { expect(villain).to respond_to(:really_evil?) }
|
191
|
-
Then { expect(villain).to respond_to(:really_evil=) }
|
192
|
-
Then { expect(villain).to be_really_evil }
|
193
|
-
end
|
194
368
|
|
195
|
-
|
196
|
-
|
197
|
-
|
369
|
+
describe "are the same as the Model's properties" do
|
370
|
+
Then { villain.properties == Marvel::Villain.properties }
|
371
|
+
end
|
372
|
+
|
373
|
+
describe "don't bleed into other types" do
|
374
|
+
Then { expect(villain).to_not respond_to(:real_name) }
|
375
|
+
end
|
198
376
|
end
|
377
|
+
|
378
|
+
describe 'hooks' do
|
379
|
+
Given(:hook_spy) { double }
|
380
|
+
Given { expect(hook_spy).to receive(hook) }
|
381
|
+
Given { allow_any_instance_of(Marvel::Sidekick).to receive(:spy).and_return(hook_spy) }
|
382
|
+
Given { Marvel::Sidekick.instance_variable_set "@_hooks", nil }
|
383
|
+
|
384
|
+
describe '.before_create' do
|
385
|
+
Given(:hook) { :before_create }
|
386
|
+
Given do
|
387
|
+
class Marvel::Sidekick < Squares::Base
|
388
|
+
before_create { spy.before_create }
|
389
|
+
end
|
390
|
+
end
|
391
|
+
When { Marvel::Sidekick.create 'Robin', catch_phrase: 'Holy _, Batman!' }
|
392
|
+
Then { 'hooked' }
|
393
|
+
end
|
394
|
+
|
395
|
+
describe '.after_create' do
|
396
|
+
Given(:hook) { :after_create }
|
397
|
+
Given do
|
398
|
+
class Marvel::Sidekick < Squares::Base
|
399
|
+
after_create { spy.after_create }
|
400
|
+
end
|
401
|
+
end
|
402
|
+
When { Marvel::Sidekick.create 'Robin', catch_phrase: 'Holy _, Batman!' }
|
403
|
+
Then { 'hooked' }
|
404
|
+
end
|
405
|
+
|
406
|
+
describe '.after_initialize' do
|
407
|
+
Given(:hook) { :after_initialize }
|
408
|
+
Given do
|
409
|
+
class Marvel::Sidekick < Squares::Base
|
410
|
+
after_initialize { spy.after_initialize }
|
411
|
+
end
|
412
|
+
end
|
413
|
+
When { Marvel::Sidekick.new 'Robin', catch_phrase: 'Holy _, Batman!' }
|
414
|
+
Then { 'hooked' }
|
415
|
+
end
|
416
|
+
|
417
|
+
describe '.after_find' do
|
418
|
+
Given(:hook) { :after_find }
|
419
|
+
Given do
|
420
|
+
class Marvel::Sidekick < Squares::Base
|
421
|
+
after_find { spy.after_find }
|
422
|
+
end
|
423
|
+
end
|
424
|
+
Given { Marvel::Sidekick.create 'Robin', catch_phrase: 'Holy _, Batman!' }
|
425
|
+
When(:result) { Marvel::Sidekick.find 'Robin' }
|
426
|
+
Then { 'hooked' }
|
427
|
+
end
|
428
|
+
|
429
|
+
describe '.before_save' do
|
430
|
+
Given(:hook) { :before_save }
|
431
|
+
Given do
|
432
|
+
class Marvel::Sidekick < Squares::Base
|
433
|
+
before_save { spy.before_save }
|
434
|
+
end
|
435
|
+
end
|
436
|
+
Given(:robin) { Marvel::Sidekick.new 'Robin', catch_phrase: 'Holy _, Batman!' }
|
437
|
+
When { robin.save }
|
438
|
+
Then { 'hooked' }
|
439
|
+
end
|
440
|
+
|
441
|
+
describe '.after_save' do
|
442
|
+
Given(:hook) { :after_save }
|
443
|
+
Given do
|
444
|
+
class Marvel::Sidekick < Squares::Base
|
445
|
+
after_save { spy.after_save }
|
446
|
+
end
|
447
|
+
end
|
448
|
+
Given(:robin) { Marvel::Sidekick.new 'Robin', catch_phrase: 'Holy _, Batman!' }
|
449
|
+
When { robin.save }
|
450
|
+
Then { 'hooked' }
|
451
|
+
end
|
452
|
+
|
453
|
+
describe '.before_destroy' do
|
454
|
+
Given(:hook) { :before_destroy }
|
455
|
+
Given do
|
456
|
+
class Marvel::Sidekick < Squares::Base
|
457
|
+
before_destroy { spy.before_destroy }
|
458
|
+
end
|
459
|
+
end
|
460
|
+
Given(:robin) { Marvel::Sidekick.create 'Robin', catch_phrase: 'Holy _, Batman!' }
|
461
|
+
When { robin.destroy }
|
462
|
+
Then { 'hooked' }
|
463
|
+
end
|
464
|
+
|
465
|
+
describe 'hook callbacks do not fire other hook callbacks' do
|
466
|
+
Given(:hook) { :after_initialize }
|
467
|
+
Given { expect(hook_spy).to_not receive(:before_save) }
|
468
|
+
Given do
|
469
|
+
class Marvel::Sidekick < Squares::Base
|
470
|
+
after_initialize do
|
471
|
+
self.spy.after_initialize
|
472
|
+
save
|
473
|
+
end
|
474
|
+
before_save { spy.before_save }
|
475
|
+
end
|
476
|
+
end
|
477
|
+
When { Marvel::Sidekick.new 'Robin', catch_phrase: 'Holy _, Batman!' }
|
478
|
+
Then { 'hooked' }
|
479
|
+
end
|
480
|
+
|
481
|
+
end # instance methods
|
199
482
|
end
|
200
483
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: squares
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joel Helbling
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-01-
|
11
|
+
date: 2015-01-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|