motion_model 0.2.5 → 0.2.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|