motion_model 0.2.5 → 0.2.6
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.
- data/CHANGELOG +8 -0
- data/README.md +103 -17
- data/lib/motion_model/model/model.rb +27 -3
- data/lib/motion_model/version.rb +1 -1
- data/spec/finder_spec.rb +140 -0
- data/spec/model_spec.rb +63 -133
- data/spec/persistence_spec.rb +2 -1
- data/spec/relation_spec.rb +1 -0
- metadata +4 -2
data/CHANGELOG
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
2012-10-14: Primary New Feature: Notifications
|
2
|
+
|
3
|
+
MotionModel: Added bulk update, which suppresses notifications and added it to delete_all.
|
4
|
+
MotionModel: Added notifications of type MotionModelDataDidChangeNotification on data change.
|
5
|
+
MotionModel: Added classification code to save to differentiate between save-new and update
|
6
|
+
MotionModel: Added notification calls to save and delete
|
7
|
+
|
8
|
+
|
1
9
|
2012-09-05: Basically rewrote how the data is stored.
|
2
10
|
|
3
11
|
The API remains consistent, but a certain amount of
|
data/README.md
CHANGED
@@ -133,12 +133,13 @@ already present, it's left alone. If it's missing, then it is created for you.
|
|
133
133
|
Each row id is guaranteed to be unique, so you can use this when communicating
|
134
134
|
with a server or syncing your rowset to a UITableView.
|
135
135
|
|
136
|
-
|
136
|
+
Using MotionModel
|
137
137
|
-----------------
|
138
138
|
|
139
|
-
*
|
140
|
-
|
141
|
-
|
139
|
+
* Your data in a model is accessed in a very ActiveRecord (or Railsey) way.
|
140
|
+
This should make transitioning from Rails or any ORM that follows the
|
141
|
+
ActiveRecord pattern pretty easy. Some of the finder syntactic sugar is
|
142
|
+
similar to that of Sequel or DataMapper.
|
142
143
|
|
143
144
|
* Finders are implemented using chaining. Here is an examples:
|
144
145
|
|
@@ -169,6 +170,13 @@ Things That Work
|
|
169
170
|
@tasks = Task.order{|one, two| two.details <=> one.details}.all # Get tasks ordered descending by :details
|
170
171
|
```
|
171
172
|
|
173
|
+
You can implement some aggregate functions using map/reduce:
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
@task.all.map{|task| task.number_of_items}.reduce(:+) # implements sum
|
177
|
+
@task.all.map{|task| task.number_of_items}.reduce(:+) / @task.count #implements average
|
178
|
+
```
|
179
|
+
|
172
180
|
* Serialization is part of MotionModel. So, in your `AppDelegate` you might do something like this:
|
173
181
|
|
174
182
|
```ruby
|
@@ -181,16 +189,18 @@ Things That Work
|
|
181
189
|
Task.serialize_to_file('tasks.dat')
|
182
190
|
end
|
183
191
|
```
|
192
|
+
After the first serialize or deserialize, your model will remember the file
|
193
|
+
name so you can call these methods without the filename argument.
|
184
194
|
|
185
|
-
|
195
|
+
Implementation note: that the this serialization of any arbitrarily complex set of relations
|
186
196
|
is automatically handled by `NSCoder` provided you conform to the coding
|
187
|
-
protocol. When you declare your columns, `MotionModel` understands how to
|
188
|
-
serialize your data so you need take no
|
197
|
+
protocol (which MotionModel does). When you declare your columns, `MotionModel` understands how to
|
198
|
+
serialize your data so you need take no specific action.
|
189
199
|
|
190
200
|
**Warning**: As of this release, persistence will serialize only one
|
191
|
-
model at a time and not your entire data store.
|
201
|
+
model at a time and not your entire data store.
|
192
202
|
|
193
|
-
* Relations
|
203
|
+
* Relations
|
194
204
|
|
195
205
|
```ruby
|
196
206
|
class Task
|
@@ -230,10 +240,86 @@ Things That Work
|
|
230
240
|
At this point, there are a few methods that need to be added
|
231
241
|
for relations, and they will.
|
232
242
|
|
233
|
-
* delete
|
234
243
|
* destroy
|
244
|
+
|
245
|
+
Notifications
|
246
|
+
-------------
|
235
247
|
|
236
|
-
|
248
|
+
Notifications are issued on object save, update, and delete. They work like this:
|
249
|
+
|
250
|
+
```ruby
|
251
|
+
def viewDidAppear(animated)
|
252
|
+
super
|
253
|
+
# other stuff here to set up your view
|
254
|
+
|
255
|
+
NSNotificationCenter.defaultCenter.addObserver(self, selector:'dataDidChange:', name:'MotionModelDataDidChangeNotification', object:nil)
|
256
|
+
end
|
257
|
+
|
258
|
+
def viewWillDisappear(animated)
|
259
|
+
super
|
260
|
+
NSNotificationCenter.defaultCenter.removeObserver self
|
261
|
+
end
|
262
|
+
|
263
|
+
# ... more stuff ...
|
264
|
+
|
265
|
+
def dataDidChange(notification)
|
266
|
+
# code to update or refresh your view based on the object passed back
|
267
|
+
# and the userInfo. userInfo keys are:
|
268
|
+
# action
|
269
|
+
# 'add'
|
270
|
+
# 'update'
|
271
|
+
# 'delete'
|
272
|
+
end
|
273
|
+
```
|
274
|
+
|
275
|
+
In your dataDidChange notification handler, you can respond to the `'MotionModelDataDidChangeNotification'` notification any way you like,
|
276
|
+
but in the instance of a tableView, you might want to use the id of the object passed back to locate
|
277
|
+
the correct row in the table and act upon it instead of doing a wholesale `reloadData`.
|
278
|
+
|
279
|
+
Note that if you do a delete_all, no notifications are issued because there is no single object
|
280
|
+
on which to report. You pretty much know what you need to do: Refresh your view.
|
281
|
+
|
282
|
+
This is implemented as a notification and not a delegate so you can dispatch something
|
283
|
+
like a remote synch operation but still be confident you will be updating the UI only on the main thread.
|
284
|
+
MotionModel does not currently send notification messages that differentiate by class, so if your
|
285
|
+
UI presents `Task`s and you get a notification that an `Assignee` has changed:
|
286
|
+
|
287
|
+
```ruby
|
288
|
+
class Task
|
289
|
+
include MotionModel::Model
|
290
|
+
has_many :assignees
|
291
|
+
# etc
|
292
|
+
end
|
293
|
+
|
294
|
+
class Assignee
|
295
|
+
include MotionModel::Model
|
296
|
+
belongs_to :task
|
297
|
+
# etc
|
298
|
+
end
|
299
|
+
|
300
|
+
# ...
|
301
|
+
|
302
|
+
task = Task.create :name => 'Walk the dog' # Triggers notification with a task object
|
303
|
+
task.assignees.create :name => 'Adam' # Triggers notification with an assignee object
|
304
|
+
|
305
|
+
# ...
|
306
|
+
|
307
|
+
# We set up observers for `MotionModelDataDidChangeNotification` someplace and:
|
308
|
+
def dataDidChange(notification)
|
309
|
+
if notification.object is_a?(Task)
|
310
|
+
# Update our UI
|
311
|
+
else
|
312
|
+
# This notification is not for us because
|
313
|
+
# We don't display anything other than tasks
|
314
|
+
end
|
315
|
+
```
|
316
|
+
|
317
|
+
The above example implies you are only presenting, say, a list of tasks in the current
|
318
|
+
view. If, however, you are presenting a list of tasks along with their assignees and
|
319
|
+
the assignees could change as a result of a background sync, then your code could and
|
320
|
+
should recognize the change to assignee objects.
|
321
|
+
|
322
|
+
Core Extensions
|
237
323
|
|
238
324
|
- String#humanize
|
239
325
|
- String#titleize
|
@@ -245,7 +331,8 @@ Things That Work
|
|
245
331
|
- Hash#empty?
|
246
332
|
- Symbol#titleize
|
247
333
|
|
248
|
-
Also in the extensions is a
|
334
|
+
Also in the extensions is a `Debug` class to log stuff to the console.
|
335
|
+
It uses NSLog so you will have a separate copy in your application log.
|
249
336
|
This may be preferable to `puts` just because it's easier to spot in
|
250
337
|
your code and it gives you the exact level and file/line number of the
|
251
338
|
info/warning/error in your console output:
|
@@ -289,7 +376,6 @@ Things That Work
|
|
289
376
|
Things In The Pipeline
|
290
377
|
----------------------
|
291
378
|
|
292
|
-
- More robust id assignment
|
293
379
|
- Adding validations and custom validations
|
294
380
|
|
295
381
|
Problems/Comments
|
@@ -304,8 +390,8 @@ one).
|
|
304
390
|
Then be sure references to motion_model are commented out or removed from your Gemfile
|
305
391
|
and/or Rakefile and put this in your Rakefile:
|
306
392
|
|
307
|
-
```
|
308
|
-
require "~/github/local
|
393
|
+
```ruby
|
394
|
+
require "~/github/local/MotionModel/lib/motion_model.rb"
|
309
395
|
```
|
310
396
|
|
311
397
|
The `~/github/local` is where I cloned it, but you can put it anyplace. Next, make
|
@@ -314,9 +400,9 @@ sure you are following the project on GitHub so you know when there are changes.
|
|
314
400
|
Submissions/Patches
|
315
401
|
------------------
|
316
402
|
|
317
|
-
Obviously, the ideal
|
403
|
+
Obviously, the ideal patch request is really a pull request from your own fork, complete with passing
|
318
404
|
specs.
|
319
405
|
|
320
|
-
Really, even a failing spec or some proposed code is fine. I really want to make
|
406
|
+
Really, for a bug report, even a failing spec or some proposed code is fine. I really want to make
|
321
407
|
this a decent tool for RubyMotion developers who need a straightforward data
|
322
408
|
modeling and persistence framework.
|
@@ -51,6 +51,20 @@ module MotionModel
|
|
51
51
|
end
|
52
52
|
|
53
53
|
module ClassMethods
|
54
|
+
# Use to do bulk insertion, updating, or deleting without
|
55
|
+
# making repeated calls to a delegate. E.g., when syncing
|
56
|
+
# with an external data source.
|
57
|
+
def bulk_update(&block)
|
58
|
+
@call_delegate_method = false
|
59
|
+
yield
|
60
|
+
@call_delegate_method = true
|
61
|
+
end
|
62
|
+
|
63
|
+
def call_delegate(object, info) #nodoc
|
64
|
+
@call_delegate_method = true if @call_delegate_method.nil?
|
65
|
+
NSNotificationCenter.defaultCenter.postNotificationName('MotionModelDataDidChangeNotification', object: object, userInfo: info) if @call_delegate_method && !object.nil?
|
66
|
+
end
|
67
|
+
|
54
68
|
def add_field(name, options, default = nil) #nodoc
|
55
69
|
col = Column.new(name, options, default)
|
56
70
|
@_columns.push col
|
@@ -217,6 +231,12 @@ module MotionModel
|
|
217
231
|
|
218
232
|
# Empties the entire store.
|
219
233
|
def delete_all
|
234
|
+
# Do each delete so any on_delete and
|
235
|
+
# cascades are called, then empty the
|
236
|
+
# collection and compact the array.
|
237
|
+
bulk_update do
|
238
|
+
self.each{|item| item.delete}
|
239
|
+
end
|
220
240
|
@collection = [] # TODO: Handle cascading or let GC take care of it.
|
221
241
|
@_next_id = 1
|
222
242
|
@collection.compact!
|
@@ -286,10 +306,10 @@ module MotionModel
|
|
286
306
|
|
287
307
|
unless options[:id]
|
288
308
|
options[:id] = self.class.next_id
|
289
|
-
self.class.increment_id
|
290
309
|
else
|
291
310
|
self.class.next_id = [options[:id].to_i, self.class.next_id].max
|
292
311
|
end
|
312
|
+
self.class.increment_id
|
293
313
|
|
294
314
|
columns.each do |col|
|
295
315
|
unless [:belongs_to, :belongs_to_id, :has_many].include? column_named(col).type
|
@@ -330,24 +350,28 @@ module MotionModel
|
|
330
350
|
def to_s
|
331
351
|
columns.each{|c| "#{c}: #{self.send(c)}\n"}
|
332
352
|
end
|
333
|
-
|
353
|
+
|
334
354
|
def save
|
335
355
|
collection = self.class.instance_variable_get('@collection')
|
336
356
|
@dirty = false
|
337
357
|
|
338
358
|
# Existing object implies update in place
|
339
359
|
# TODO: Optimize location of existing id
|
360
|
+
action = 'add'
|
340
361
|
if obj = collection.find{|o| o.id == @data[:id]}
|
341
|
-
|
362
|
+
collection = self
|
363
|
+
action = 'update'
|
342
364
|
else
|
343
365
|
collection << self
|
344
366
|
end
|
367
|
+
self.class.call_delegate(self, :action => action)
|
345
368
|
end
|
346
369
|
|
347
370
|
def delete
|
348
371
|
collection = self.class.instance_variable_get('@collection')
|
349
372
|
target_index = collection.index{|item| item.id == self.id}
|
350
373
|
collection.delete_at(target_index)
|
374
|
+
self.class.call_delegate(self, :action => 'delete')
|
351
375
|
end
|
352
376
|
|
353
377
|
def length
|
data/lib/motion_model/version.rb
CHANGED
data/spec/finder_spec.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
class Task
|
2
|
+
include MotionModel::Model
|
3
|
+
columns :name => :string,
|
4
|
+
:details => :string,
|
5
|
+
:some_day => :date
|
6
|
+
end
|
7
|
+
|
8
|
+
describe 'finders' do
|
9
|
+
before do
|
10
|
+
Task.delete_all
|
11
|
+
1.upto(10) {|i| Task.create(:name => "task #{i}", :id => i)}
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'find' do
|
15
|
+
it 'finds elements within the collection' do
|
16
|
+
Task.count.should == 10
|
17
|
+
Task.find(3).name.should.equal("task 3")
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'returns nil if find by id is not found' do
|
21
|
+
Task.find(999).should.be.nil
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'looks into fields if field name supplied' do
|
25
|
+
Task.create(:name => 'find me')
|
26
|
+
tasks = Task.find(:name).eq('find me')
|
27
|
+
tasks.count.should.equal(1)
|
28
|
+
tasks.first.name.should == 'find me'
|
29
|
+
end
|
30
|
+
|
31
|
+
it "provides an array of valid model instances when doing a find" do
|
32
|
+
Task.create(:name => 'find me')
|
33
|
+
tasks = Task.find(:name).eq('find me')
|
34
|
+
tasks.first.name.should.eql 'find me'
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'allows for multiple (chained) query parameters' do
|
38
|
+
Task.create(:name => 'find me', :details => "details 1")
|
39
|
+
Task.create(:name => 'find me', :details => "details 2")
|
40
|
+
tasks = Task.find(:name).eq('find me').and(:details).like('2')
|
41
|
+
tasks.first.details.should.equal('details 2')
|
42
|
+
tasks.all.length.should.equal(1)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'where should respond to finder methods' do
|
46
|
+
Task.where(:details).should.respond_to(:contain)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'returns a FinderQuery object' do
|
50
|
+
Task.where(:details).should.is_a(MotionModel::FinderQuery)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'using where instead of find' do
|
54
|
+
atask = Task.create(:name => 'find me', :details => "details 1")
|
55
|
+
found_task = Task.where(:details).contain("s 1").first.details.should == 'details 1'
|
56
|
+
end
|
57
|
+
|
58
|
+
it "performs set inclusion(in) queries" do
|
59
|
+
class InTest
|
60
|
+
include MotionModel::Model
|
61
|
+
columns :name
|
62
|
+
end
|
63
|
+
|
64
|
+
1.upto(10) do |i|
|
65
|
+
InTest.create(:id => i, :name => "test #{i}")
|
66
|
+
end
|
67
|
+
|
68
|
+
results = InTest.find(:id).in([3, 5, 7])
|
69
|
+
results.length.should == 3
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'handles case-sensitive queries' do
|
73
|
+
task = Task.create :name => 'Bob'
|
74
|
+
Task.find(:name).eq('bob', :case_sensitive => true).all.length.should == 0
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'all returns all members of the collection as an array' do
|
78
|
+
Task.all.length.should.equal(10)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'each yields each row in sequence' do
|
82
|
+
task_id = nil
|
83
|
+
Task.each do |task|
|
84
|
+
task_id.should.<(task.id) if task_id
|
85
|
+
task_id = task.id
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe 'block-style finders' do
|
90
|
+
before do
|
91
|
+
@items_less_than_5 = Task.find{|item| item.name.split(' ').last.to_i < 5}
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'returns a FinderQuery' do
|
95
|
+
@items_less_than_5.should.is_a MotionModel::FinderQuery
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'handles block-style finders' do
|
99
|
+
@items_less_than_5.length.should == 4
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'deals with any arbitrary block finder' do
|
103
|
+
@even_items = Task.find do |item|
|
104
|
+
test_item = item.name.split(' ').last.to_i
|
105
|
+
test_item % 2 == 0 && test_item <= 6
|
106
|
+
end
|
107
|
+
@even_items.each{|item| item.name.split(' ').last.to_i.should.even?}
|
108
|
+
@even_items.length.should == 3 # [2, 4, 6]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe 'sorting' do
|
114
|
+
before do
|
115
|
+
Task.delete_all
|
116
|
+
Task.create(:name => 'Task 3', :details => 'detail 3')
|
117
|
+
Task.create(:name => 'Task 1', :details => 'detail 1')
|
118
|
+
Task.create(:name => 'Task 2', :details => 'detail 6')
|
119
|
+
Task.create(:name => 'Random Task', :details => 'another random task')
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'sorts by field' do
|
123
|
+
tasks = Task.order(:name).all
|
124
|
+
tasks[0].name.should.equal('Random Task')
|
125
|
+
tasks[1].name.should.equal('Task 1')
|
126
|
+
tasks[2].name.should.equal('Task 2')
|
127
|
+
tasks[3].name.should.equal('Task 3')
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'sorts observing block syntax' do
|
131
|
+
tasks = Task.order{|one, two| two.details <=> one.details}.all
|
132
|
+
tasks[0].details.should.equal('detail 6')
|
133
|
+
tasks[1].details.should.equal('detail 3')
|
134
|
+
tasks[2].details.should.equal('detail 1')
|
135
|
+
tasks[3].details.should.equal('another random task')
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
data/spec/model_spec.rb
CHANGED
@@ -131,138 +131,6 @@ describe "Creating a model" do
|
|
131
131
|
|
132
132
|
end
|
133
133
|
|
134
|
-
describe 'finders' do
|
135
|
-
before do
|
136
|
-
Task.delete_all
|
137
|
-
@tasks = []
|
138
|
-
10.times {|i| @tasks.push Task.create(:name => "task #{i}")}
|
139
|
-
end
|
140
|
-
|
141
|
-
describe 'find' do
|
142
|
-
it 'finds elements within the collection' do
|
143
|
-
task = Task.find(3).name.should.equal(@tasks[2].name) # zero based
|
144
|
-
end
|
145
|
-
|
146
|
-
it 'returns nil if find by id is not found' do
|
147
|
-
Task.find(999).should.be.nil
|
148
|
-
end
|
149
|
-
|
150
|
-
it 'looks into fields if field name supplied' do
|
151
|
-
Task.create(:name => 'find me')
|
152
|
-
tasks = Task.find(:name).eq('find me')
|
153
|
-
tasks.all.length.should.equal(1)
|
154
|
-
tasks.first.name.should == 'find me'
|
155
|
-
end
|
156
|
-
|
157
|
-
it "provides an array of valid model instances when doing a find" do
|
158
|
-
Task.create(:name => 'find me')
|
159
|
-
tasks = Task.find(:name).eq('find me')
|
160
|
-
tasks.first.name.should.eql 'find me'
|
161
|
-
end
|
162
|
-
|
163
|
-
it 'allows for multiple (chained) query parameters' do
|
164
|
-
Task.create(:name => 'find me', :details => "details 1")
|
165
|
-
Task.create(:name => 'find me', :details => "details 2")
|
166
|
-
tasks = Task.find(:name).eq('find me').and(:details).like('2')
|
167
|
-
tasks.first.details.should.equal('details 2')
|
168
|
-
tasks.all.length.should.equal(1)
|
169
|
-
end
|
170
|
-
|
171
|
-
it 'where should respond to finder methods' do
|
172
|
-
Task.where(:details).should.respond_to(:contain)
|
173
|
-
end
|
174
|
-
|
175
|
-
it 'returns a FinderQuery object' do
|
176
|
-
Task.where(:details).should.is_a(MotionModel::FinderQuery)
|
177
|
-
end
|
178
|
-
|
179
|
-
it 'using where instead of find' do
|
180
|
-
atask = Task.create(:name => 'find me', :details => "details 1")
|
181
|
-
found_task = Task.where(:details).contain("s 1").first.details.should == 'details 1'
|
182
|
-
end
|
183
|
-
|
184
|
-
it "performs set inclusion(in) queries" do
|
185
|
-
class InTest
|
186
|
-
include MotionModel::Model
|
187
|
-
columns :name
|
188
|
-
end
|
189
|
-
|
190
|
-
1.upto(10) do |i|
|
191
|
-
InTest.create(:id => i, :name => "test #{i}")
|
192
|
-
end
|
193
|
-
|
194
|
-
results = InTest.find(:id).in([3, 5, 7])
|
195
|
-
results.length.should == 3
|
196
|
-
end
|
197
|
-
|
198
|
-
it 'handles case-sensitive queries' do
|
199
|
-
task = Task.create :name => 'Bob'
|
200
|
-
Task.find(:name).eq('bob', :case_sensitive => true).all.length.should == 0
|
201
|
-
end
|
202
|
-
|
203
|
-
it 'all returns all members of the collection as an array' do
|
204
|
-
Task.all.length.should.equal(10)
|
205
|
-
end
|
206
|
-
|
207
|
-
it 'each yields each row in sequence' do
|
208
|
-
i = 0
|
209
|
-
Task.each do |task|
|
210
|
-
task.name.should.equal("task #{i}")
|
211
|
-
i += 1
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
describe 'block-style finders' do
|
216
|
-
before do
|
217
|
-
@items_less_than_5 = Task.find{|item| item.name.split(' ').last.to_i < 5}
|
218
|
-
end
|
219
|
-
|
220
|
-
it 'returns a FinderQuery' do
|
221
|
-
@items_less_than_5.should.is_a MotionModel::FinderQuery
|
222
|
-
end
|
223
|
-
|
224
|
-
it 'handles block-style finders' do
|
225
|
-
@items_less_than_5.length.should == 5 # Zero based
|
226
|
-
end
|
227
|
-
|
228
|
-
it 'deals with any arbitrary block finder' do
|
229
|
-
@even_items = Task.find do |item|
|
230
|
-
test_item = item.name.split(' ').last.to_i
|
231
|
-
test_item % 2 == 0 && test_item < 5
|
232
|
-
end
|
233
|
-
@even_items.each{|item| item.name.split(' ').last.to_i.should.even?}
|
234
|
-
@even_items.length.should == 3 # [0, 2, 4]
|
235
|
-
end
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
describe 'sorting' do
|
240
|
-
before do
|
241
|
-
Task.delete_all
|
242
|
-
Task.create(:name => 'Task 3', :details => 'detail 3')
|
243
|
-
Task.create(:name => 'Task 1', :details => 'detail 1')
|
244
|
-
Task.create(:name => 'Task 2', :details => 'detail 6')
|
245
|
-
Task.create(:name => 'Random Task', :details => 'another random task')
|
246
|
-
end
|
247
|
-
|
248
|
-
it 'sorts by field' do
|
249
|
-
tasks = Task.order(:name).all
|
250
|
-
tasks[0].name.should.equal('Random Task')
|
251
|
-
tasks[1].name.should.equal('Task 1')
|
252
|
-
tasks[2].name.should.equal('Task 2')
|
253
|
-
tasks[3].name.should.equal('Task 3')
|
254
|
-
end
|
255
|
-
|
256
|
-
it 'sorts observing block syntax' do
|
257
|
-
tasks = Task.order{|one, two| two.details <=> one.details}.all
|
258
|
-
tasks[0].details.should.equal('detail 6')
|
259
|
-
tasks[1].details.should.equal('detail 3')
|
260
|
-
tasks[2].details.should.equal('detail 1')
|
261
|
-
tasks[3].details.should.equal('another random task')
|
262
|
-
end
|
263
|
-
end
|
264
|
-
|
265
|
-
end
|
266
134
|
|
267
135
|
describe 'deleting' do
|
268
136
|
before do
|
@@ -272,7 +140,7 @@ describe "Creating a model" do
|
|
272
140
|
it 'deletes a row' do
|
273
141
|
target = Task.find(:name).eq('task 3').first
|
274
142
|
target.delete
|
275
|
-
Task.find(:
|
143
|
+
Task.find(:name).eq('task 3').count.should.equal 0
|
276
144
|
end
|
277
145
|
|
278
146
|
it 'deleting a row changes length' do
|
@@ -351,3 +219,65 @@ describe "Creating a model" do
|
|
351
219
|
end
|
352
220
|
end
|
353
221
|
|
222
|
+
class NotifiableTask
|
223
|
+
include MotionModel::Model
|
224
|
+
columns :name
|
225
|
+
has_many :assignees
|
226
|
+
|
227
|
+
attr_accessor :notification_called, :notification_details
|
228
|
+
|
229
|
+
def hookup_events
|
230
|
+
NSNotificationCenter.defaultCenter.addObserver(self, selector:'dataDidChange:', name:'MotionModelDataDidChangeNotification', object:nil)
|
231
|
+
@notification_details = nil
|
232
|
+
end
|
233
|
+
|
234
|
+
def dataDidChange(notification)
|
235
|
+
@notification_called = true
|
236
|
+
@notification_details = notification.userInfo
|
237
|
+
end
|
238
|
+
|
239
|
+
def teardown_events
|
240
|
+
NSNotificationCenter.defaultCenter.removeObserver self
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
describe 'data change notifications' do
|
245
|
+
before do
|
246
|
+
@task = NotifiableTask.new
|
247
|
+
@task.hookup_events
|
248
|
+
end
|
249
|
+
|
250
|
+
after do
|
251
|
+
@task.teardown_events
|
252
|
+
end
|
253
|
+
|
254
|
+
it "fires a change notification when an item is added" do
|
255
|
+
@task.notification_called = false
|
256
|
+
lambda{@task.save}.should.change{@task.notification_called}
|
257
|
+
end
|
258
|
+
|
259
|
+
it "contains an add notification for new objects" do
|
260
|
+
@task.save
|
261
|
+
@task.notification_details[:action].should == 'add'
|
262
|
+
end
|
263
|
+
|
264
|
+
it "contans an update notification for an updated object" do
|
265
|
+
@task.save
|
266
|
+
@task.name = "Bill"
|
267
|
+
@task.save
|
268
|
+
@task.notification_details[:action].should == 'update'
|
269
|
+
end
|
270
|
+
|
271
|
+
it "contains a delete notification for a deleted object" do
|
272
|
+
@task.save
|
273
|
+
@task.delete
|
274
|
+
@task.notification_details[:action].should == 'delete'
|
275
|
+
end
|
276
|
+
|
277
|
+
it "does not get a delete notification for delete_all" do
|
278
|
+
@task = NotifiableTask.create :name => 'Bob'
|
279
|
+
@task.notification_called = nil
|
280
|
+
lambda{NotifiableTask.delete_all}.should.not.change{@task.notification_called}
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
data/spec/persistence_spec.rb
CHANGED
data/spec/relation_spec.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: motion_model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.6
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-10-
|
12
|
+
date: 2012-10-24 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Simple model and validation mixins for RubyMotion
|
15
15
|
email:
|
@@ -34,6 +34,7 @@ files:
|
|
34
34
|
- lib/motion_model/version.rb
|
35
35
|
- motion_model.gemspec
|
36
36
|
- spec/ext_spec.rb
|
37
|
+
- spec/finder_spec.rb
|
37
38
|
- spec/model_spec.rb
|
38
39
|
- spec/persistence_spec.rb
|
39
40
|
- spec/relation_spec.rb
|
@@ -63,6 +64,7 @@ specification_version: 3
|
|
63
64
|
summary: Simple model and validation mixins for RubyMotion
|
64
65
|
test_files:
|
65
66
|
- spec/ext_spec.rb
|
67
|
+
- spec/finder_spec.rb
|
66
68
|
- spec/model_spec.rb
|
67
69
|
- spec/persistence_spec.rb
|
68
70
|
- spec/relation_spec.rb
|