squares 0.2.9 → 0.3.0
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.
- 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
|