motion_model 0.3.6 → 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/sxross/MotionModel)
1
+ [![Code Climate](https://codeclimate.com/github/sxross/MotionModel.png)](https://codeclimate.com/github/sxross/MotionModel)
2
2
 
3
3
  MotionModel -- Simple Model, Validation, and Input Mixins for RubyMotion
4
4
  ================
@@ -17,7 +17,7 @@ File | Module | Description
17
17
 
18
18
  MotionModel is MIT licensed, which means you can pretty much do whatever
19
19
  you like with it. See the LICENSE file in this project.
20
-
20
+
21
21
  * [Getting Going](#getting-going)
22
22
  * [What Model Can Do](#what-model-can-do)
23
23
  * [Model Data Types](#model-data-types)
@@ -87,11 +87,11 @@ Models support default values, so if you specify your model like this, you get d
87
87
  ```ruby
88
88
  class Task
89
89
  include MotionModel::Model
90
-
90
+
91
91
  columns :name => :string,
92
92
  :due_date => {:type => :date, :default => '2012-09-15'}
93
93
  end
94
- ```
94
+ ```
95
95
 
96
96
  You can also include the `Validatable` module to get field validation. For example:
97
97
 
@@ -197,11 +197,19 @@ You are responsible for adding an error message using:
197
197
 
198
198
  You must return `true` from your validator if the value passes validation otherwise `false`.
199
199
 
200
+ An important note about `save` once you include `Validatable`, you have two flavors
201
+ of save:
202
+
203
+ Method | Meaning
204
+ -----------------------|---------------------------
205
+ `save(options)` |Just saves the data if it is valid (passes validations) or if you have specified `:validate => false`
206
+ `save!` |Saves the data if it is valid, otherwise raises a `MotionModel::Validatable::RecordInvalid` exception
207
+
200
208
  Model Instances and Unique IDs
201
209
  -----------------
202
210
 
203
- It is assumed that models can be created from an external source (JSON from a Web
204
- application or `NSCoder` from the device) or simply be a stand-alone data store.
211
+ It is assumed that models can be created from an external source (JSON from a Web
212
+ application or `NSCoder` from the device) or simply be a stand-alone data store.
205
213
  To identify rows properly, the model tracks a special field called `:id`. If it's
206
214
  already present, it's left alone. If it's missing, then it is created for you.
207
215
  Each row id is guaranteed to be unique, so you can use this when communicating
@@ -214,24 +222,24 @@ Using MotionModel
214
222
  This should make transitioning from Rails or any ORM that follows the
215
223
  ActiveRecord pattern pretty easy. Some of the finder syntactic sugar is
216
224
  similar to that of Sequel or DataMapper.
217
-
225
+
218
226
  * Finders are implemented using chaining. Here is an examples:
219
227
 
220
- ```ruby
228
+ ```ruby
221
229
  @tasks = Task.where(:assigned_to).eq('bob').and(:location).contains('seattle')
222
230
  @tasks.all.each { |task| do_something_with(task) }
223
231
  ```
224
-
232
+
225
233
  You can use a block with find:
226
-
227
- ```ruby
234
+
235
+ ```ruby
228
236
  @tasks = Task.find{|task| task.name =~ /dog/i && task.assigned_to == 'Bob'}
229
237
  ```
230
-
238
+
231
239
  Note that finders always return a proxy (`FinderQuery`). You must use `first`, `last`, or `all`
232
240
  to get useful results.
233
-
234
- ```ruby
241
+
242
+ ```ruby
235
243
  @tasks = Task.where(:owner).eq('jim') # => A FinderQuery.
236
244
  @tasks.all # => An array of matching results.
237
245
  @tasks.first # => The first result
@@ -256,68 +264,68 @@ Using MotionModel
256
264
  ```ruby
257
265
  @tasks = Task.deserialize_from_file('tasks.dat')
258
266
  ```
259
-
267
+
260
268
  and of course on the "save" side:
261
-
269
+
262
270
  ```ruby
263
271
  Task.serialize_to_file('tasks.dat')
264
272
  end
265
273
  ```
266
274
  After the first serialize or deserialize, your model will remember the file
267
275
  name so you can call these methods without the filename argument.
268
-
276
+
269
277
  Implementation note: that the this serialization of any arbitrarily complex set of relations
270
278
  is automatically handled by `NSCoder` provided you conform to the coding
271
279
  protocol (which MotionModel does). When you declare your columns, `MotionModel` understands how to
272
280
  serialize your data so you need take no specific action.
273
-
281
+
274
282
  **Warning**: As of this release, persistence will serialize only one
275
283
  model at a time and not your entire data store.
276
-
284
+
277
285
  * Relations
278
-
286
+
279
287
  ```ruby
280
288
  class Task
281
289
  include MotionModel::Model
282
290
  columns :name => :string
283
291
  has_many :assignees
284
292
  end
285
-
293
+
286
294
  class Assignee
287
295
  include MotionModel::Model
288
296
  columns :assignee_name => :string
289
297
  belongs_to :task
290
298
  end
291
-
299
+
292
300
  # Create a task, then create an assignee as a
293
301
  # related object on that task
294
302
  a_task = Task.create(:name => "Walk the Dog")
295
303
  a_task.assignees.create(:assignee_name => "Howard")
296
-
304
+
297
305
  # See? It works.
298
306
  a_task.assignees.assignee_name # => "Howard"
299
307
  Task.first.assignees.assignee_name # => "Howard"
300
-
308
+
301
309
  # Create another assignee but don't save
302
310
  # Add to assignees collection. Both objects
303
311
  # are saved.
304
312
  another_assignee = Assignee.new(:name => "Douglas")
305
313
  a_task.assignees << another_assignee # adds to relation and saves both objects
306
-
314
+
307
315
  # The count of assignees accurately reflects current state
308
316
  a_task.assignees.count # => 2
309
-
317
+
310
318
  # And backreference access through belongs_to works.
311
319
  Assignee.first.task.name # => "Walk the Dog"
312
320
  ```
313
-
321
+
314
322
  There are four ways to delete objects from your data store:
315
323
 
316
324
  * `object.delete #` just deletes the object and ignores all relations
317
325
  * `object.destroy #` deletes the object and honors any cascading declarations
318
326
  * `Class.delete_all #` just deletes all objects of this class and ignores all relations
319
327
  * `Class.destroy_all #` deletes all objects of this class and honors any cascading declarations
320
-
328
+
321
329
  The key to how the `destroy` variants work in how the relation is declared. You can declare:
322
330
 
323
331
  ```ruby
@@ -379,14 +387,14 @@ Notifications
379
387
  -------------
380
388
 
381
389
  Notifications are issued on object save, update, and delete. They work like this:
382
-
390
+
383
391
  ```ruby
384
392
  def viewDidAppear(animated)
385
393
  super
386
394
  # other stuff here to set up your view
387
-
388
- NSNotificationCenter.defaultCenter.addObserver(self, selector:'dataDidChange:',
389
- name:'MotionModelDataDidChangeNotification',
395
+
396
+ NSNotificationCenter.defaultCenter.addObserver(self, selector:'dataDidChange:',
397
+ name:'MotionModelDataDidChangeNotification',
390
398
  object:nil)
391
399
  end
392
400
 
@@ -406,11 +414,11 @@ def dataDidChange(notification)
406
414
  # 'delete'
407
415
  end
408
416
  ```
409
-
417
+
410
418
  In your `dataDidChange` notification handler, you can respond to the `MotionModelDataDidChangeNotification` notification any way you like,
411
419
  but in the instance of a tableView, you might want to use the id of the object passed back to locate
412
420
  the correct row in the table and act upon it instead of doing a wholesale `reloadData`.
413
-
421
+
414
422
  Note that if you do a delete_all, no notifications are issued because there is no single object
415
423
  on which to report. You pretty much know what you need to do: Refresh your view.
416
424
 
@@ -418,7 +426,7 @@ end
418
426
  like a remote synch operation but still be confident you will be updating the UI only on the main thread.
419
427
  MotionModel does not currently send notification messages that differentiate by class, so if your
420
428
  UI presents `Task`s and you get a notification that an `Assignee` has changed:
421
-
429
+
422
430
  ```ruby
423
431
  class Task
424
432
  include MotionModel::Model
@@ -448,12 +456,12 @@ else
448
456
  # We don't display anything other than tasks
449
457
  end
450
458
  ```
451
-
459
+
452
460
  The above example implies you are only presenting, say, a list of tasks in the current
453
461
  view. If, however, you are presenting a list of tasks along with their assignees and
454
462
  the assignees could change as a result of a background sync, then your code could and
455
463
  should recognize the change to assignee objects.
456
-
464
+
457
465
  Core Extensions
458
466
  ----------------
459
467
 
@@ -466,47 +474,47 @@ Core Extensions
466
474
  - Array#empty?
467
475
  - Hash#empty?
468
476
  - Symbol#titleize
469
-
477
+
470
478
  Also in the extensions is a `Debug` class to log stuff to the console.
471
479
  It uses NSLog so you will have a separate copy in your application log.
472
480
  This may be preferable to `puts` just because it's easier to spot in
473
481
  your code and it gives you the exact level and file/line number of the
474
482
  info/warning/error in your console output:
475
-
483
+
476
484
  - Debug.info(message)
477
485
  - Debug.warning(message)
478
486
  - Debug.error(message)
479
487
  - Debug.silence / Debug.resume to turn on and off logging
480
488
  - Debug.colorize (true/false) for pretty console display
481
-
489
+
482
490
  Finally, there is an inflector singleton class based around the one
483
491
  Rails has implemented. You don't need to dig around in this class
484
492
  too much, as its core functionality is exposed through two methods:
485
-
493
+
486
494
  String#singularize
487
495
  String#pluralize
488
-
496
+
489
497
  These work, with the caveats that 1) The inflector is English-language
490
498
  based; 2) Irregular nouns are not handled; 3) Singularizing a singular
491
499
  or pluralizing a plural makes for good cocktail-party stuff, but in
492
500
  code, it mangles things pretty badly.
493
-
501
+
494
502
  You may want to get into customizing your inflections using:
495
-
503
+
496
504
  - Inflector.inflections.singular(rule, replacement)
497
505
  - Inflector.inflections.plural(rule, replacement)
498
506
  - Inflector.inflections.irregular(rule, replacement)
499
-
507
+
500
508
  These allow you to add to the list of rules the inflector uses when
501
509
  processing singularize and pluralize. For each singular rule, you will
502
510
  probably want to add a plural one. Note that order matters for rules,
503
511
  so if your inflection is getting chewed up in one of the baked-in
504
512
  inflections, you may have to use Inflector.inflections.reset to empty
505
513
  them all out and build your own.
506
-
514
+
507
515
  Of particular note is Inflector.inflections.irregular. This is for words
508
516
  that defy regular rules such as 'man' => 'men' or 'person' => 'people'.
509
- Again, a reversing rule is required for both singularize and
517
+ Again, a reversing rule is required for both singularize and
510
518
  pluralize to work properly.
511
519
 
512
520
  Formotion Support
@@ -64,31 +64,87 @@ class Inflector
64
64
  # Put singular-form to plural form transformations here
65
65
  @plurals = [
66
66
  [/^person$/, 'people'],
67
- [/^identity$/, 'identities'],
67
+ [/^man$/, 'men'],
68
68
  [/^child$/, 'children'],
69
- [/^(.*)ee$/i, '\1ees'], # attendee => attendees
70
- [/^(.*)us$/i, '\1i'], # alumnus => alumni
71
- [/^(.*s)$/i, '\1es'], # pass => passes
72
- [/^(.*)$/, '\1s'] # normal => normals
69
+ [/^sex$/, 'sexes'],
70
+ [/^move$/, 'moves'],
71
+ [/^cow$/, 'kine'],
72
+ [/^zombie$/, 'zombies'],
73
+ [/(quiz)$/i, '\1zes'],
74
+ [/^(oxen)$/i, '\1'],
75
+ [/^(ox)$/i, '\1en'],
76
+ [/^(m|l)ice$/i, '\1ice'],
77
+ [/^(m|l)ouse$/i, '\1ice'],
78
+ [/(matr|vert|ind)(?:ix|ex)$/i, '\1ices'],
79
+ [/(x|ch|ss|sh)$/i, '\1es'],
80
+ [/([^aeiouy]|qu)y$/i, '\1ies'],
81
+ [/(hive)$/i, '\1s'],
82
+ [/(?:([^f])fe|([lr])f)$/i, '\1\2ves'],
83
+ [/sis$/i, 'ses'],
84
+ [/([ti])a$/i, '\1a'],
85
+ [/([ti])um$/i, '\1a'],
86
+ [/(buffal|tomat)o$/i, '\1oes'],
87
+ [/(bu)s$/i, '\1ses'],
88
+ [/(alias|status)$/i, '\1es'],
89
+ [/(octop|vir)i$/i, '\1i'],
90
+ [/(octop|vir|alumn)us$/i, '\1i'],
91
+ [/^(ax|test)is$/i, '\1es'],
92
+ [/s$/i, 's'],
93
+ [/$/, 's']
73
94
  ]
74
95
 
75
96
  # Put plural-form to singular form transformations here
76
97
  @singulars = [
77
98
  [/^people$/, 'person'],
78
- [/^identities$/, 'identity'],
99
+ [/^men$/, 'man'],
79
100
  [/^children$/, 'child'],
80
- [/^(.*)ees$/i, '\1ee'], # attendees => attendee
81
- [/^(.*)es$/i, '\1'], # passes => pass
82
- [/^(.*)i$/i, '\1us'], # alumni => alumnus
83
- [/^(.*)s$/i, '\1'] # normals => normal
101
+ [/^sexes$/, 'sex'],
102
+ [/^moves$/, 'move'],
103
+ [/^kine$/, 'cow'],
104
+ [/^zombies$/, 'zombie'],
105
+ [/(database)s$/i, '\1'],
106
+ [/(quiz)zes$/i, '\1'],
107
+ [/(matr)ices$/i, '\1ix'],
108
+ [/(vert|ind)ices$/i, '\1ex'],
109
+ [/^(ox)en/i, '\1'],
110
+ [/(alias|status)(es)?$/i, '\1'],
111
+ [/(octop|vir|alumn)(us|i)$/i, '\1us'],
112
+ [/^(a)x[ie]s$/i, '\1xis'],
113
+ [/(cris|test)(is|es)$/i, '\1is'],
114
+ [/(shoe)s$/i, '\1'],
115
+ [/(o)es$/i, '\1'],
116
+ [/(bus)(es)?$/i, '\1'],
117
+ [/^(m|l)ice$/i, '\1ouse'],
118
+ [/(x|ch|ss|sh)es$/i, '\1'],
119
+ [/(m)ovies$/i, '\1ovie'],
120
+ [/(s)eries$/i, '\1eries'],
121
+ [/([^aeiouy]|qu)ies$/i, '\1y'],
122
+ [/([lr])ves$/i, '\1f'],
123
+ [/(tive)s$/i, '\1'],
124
+ [/(hive)s$/i, '\1'],
125
+ [/([^f])ves$/i, '\1fe'],
126
+ [/(^analy)(sis|ses)$/i, '\1sis'],
127
+ [/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1sis'],
128
+ [/([ti])a$/i, '\1um'],
129
+ [/(n)ews$/i, '\1ews'],
130
+ [/(ss)$/i, '\1'],
131
+ [/s$/i, '']
84
132
  ]
85
133
 
86
134
  @irregulars = [
87
135
  ]
88
136
 
89
137
  @uncountables = [
138
+ 'equipment',
139
+ 'information',
140
+ 'rice',
141
+ 'money',
142
+ 'species',
143
+ 'series',
90
144
  'fish',
91
- 'sheep'
145
+ 'sheep',
146
+ 'jeans',
147
+ 'police'
92
148
  ]
93
149
  end
94
150
 
@@ -3,6 +3,7 @@ module MotionModel
3
3
  FORMOTION_MAP = {
4
4
  :string => :string,
5
5
  :date => :date,
6
+ :time => :date,
6
7
  :int => :number,
7
8
  :integer => :number,
8
9
  :float => :number,
@@ -31,9 +32,14 @@ module MotionModel
31
32
  }
32
33
  end
33
34
 
35
+ def is_date_time?(column)
36
+ column_type = type(column)
37
+ [:date, :time].include?(column_type)
38
+ end
39
+
34
40
  def value_for(column) #nodoc
35
41
  value = self.send(column)
36
- value = value.to_f if type(column) == :date && !value.nil?
42
+ value = value.to_f if value && is_date_time?(column)
37
43
  value
38
44
  end
39
45
 
@@ -66,14 +72,14 @@ module MotionModel
66
72
  end
67
73
  form
68
74
  end
69
-
75
+
70
76
  # <tt>from_formotion</tt> takes the information rendered from a Formotion
71
77
  # form and stuffs it back into a MotionModel. This data is not saved until
72
78
  # you say so, offering you the opportunity to validate your form data.
73
79
  def from_formotion!(data)
74
80
  self.returnable_columns.each{|column|
75
- if type(column) == :date && !data[column].nil?
76
- data[column] = Time.at(data[column])
81
+ if data[column] && type(column) == :date || type(column) == :time
82
+ data[column] = Time.at(data[column]) unless data[column].nil?
77
83
  end
78
84
  value = self.send("#{column}=", data[column])
79
85
  }
@@ -20,9 +20,13 @@
20
20
  # Recognized types are:
21
21
  #
22
22
  # * :string
23
+ # * :text
23
24
  # * :date (must be in YYYY-mm-dd form)
25
+ # * :time
24
26
  # * :integer
25
27
  # * :float
28
+ # * :boolean
29
+ # * :array
26
30
  #
27
31
  # Assuming you have a bunch of tasks in your data store, you can do this:
28
32
  #
@@ -187,7 +191,7 @@ module MotionModel
187
191
  def destroy_all
188
192
  ids = self.all.map{|item| item.id}
189
193
  bulk_update do
190
- ids.each do |item|
194
+ ids.each do |item|
191
195
  find(item).destroy
192
196
  end
193
197
  end
@@ -396,7 +400,7 @@ module MotionModel
396
400
  # Save current object. Speaking from the context of relational
397
401
  # databases, this inserts a row if it's a new one, or updates
398
402
  # in place if not.
399
- def save
403
+ def save(*)
400
404
  call_hooks 'save' do
401
405
  @dirty = false
402
406
 
@@ -435,7 +439,7 @@ module MotionModel
435
439
  def delete
436
440
  call_hooks('delete') do
437
441
  target_index = collection.index{|item| item.id == self.id}
438
- collection.delete_at(target_index)
442
+ collection.delete_at(target_index) unless target_index.nil?
439
443
  self.class.issue_notification(self, :action => 'delete')
440
444
  end
441
445
  end
@@ -45,7 +45,7 @@ module MotionModel
45
45
  when :boolean, :bool then cast_to_bool(arg)
46
46
  when :int, :integer, :belongs_to_id then cast_to_integer(arg)
47
47
  when :float, :double then cast_to_float(arg)
48
- when :date then cast_to_date(arg)
48
+ when :date, :time then cast_to_date(arg)
49
49
  when :text then cast_to_string(arg)
50
50
  when :array then cast_to_array(arg)
51
51
  else
@@ -38,7 +38,7 @@ module MotionModel
38
38
  # want to use. If you omit this, it will use the last
39
39
  # remembered file name.
40
40
  #
41
- # Raises a +MotionModel::PersistFileFailureError+ on failure.
41
+ # Raises a +MotionModel::PersistFileError+ on failure.
42
42
  def serialize_to_file(file_name = nil)
43
43
  @file_name = file_name if file_name
44
44
  error_ptr = Pointer.new(:object)
@@ -49,7 +49,7 @@ module MotionModel
49
49
  error = error_ptr[0]
50
50
 
51
51
  # Now we can use the `error' object.
52
- raise MotionModel::PersistFileFailureError.new "Error when writing data: #{error}"
52
+ raise MotionModel::PersistFileError.new "Error when writing data: #{error}"
53
53
  end
54
54
  end
55
55
 
@@ -1,6 +1,8 @@
1
1
  module MotionModel
2
2
  module Validatable
3
3
  class ValidationSpecificationError < RuntimeError; end
4
+ class RecordInvalid < RuntimeError; end
5
+
4
6
 
5
7
  def self.included(base)
6
8
  base.extend(ClassMethods)
@@ -13,6 +15,8 @@ module MotionModel
13
15
  ex = ValidationSpecificationError.new('field not present in validation call')
14
16
  raise ex
15
17
  end
18
+
19
+
16
20
 
17
21
  if validation_type == {}
18
22
  ex = ValidationSpecificationError.new('validation type not present or not a hash')
@@ -28,6 +32,17 @@ module MotionModel
28
32
  end
29
33
  end
30
34
 
35
+ # It doesn't save when validations fails
36
+ def save(options={ :validate => true})
37
+ (valid? || !options[:validate]) ? super : false
38
+ end
39
+
40
+ # it fails loudly
41
+ def save!
42
+ raise RecordInvalid.new('failed validation') unless valid?
43
+ save
44
+ end
45
+
31
46
  # This has two functions:
32
47
  #
33
48
  # * First, it triggers validations.
@@ -1,4 +1,4 @@
1
1
 
2
2
  module MotionModel
3
- VERSION = "0.3.6"
3
+ VERSION = "0.3.7"
4
4
  end
data/spec/ext_spec.rb CHANGED
@@ -3,38 +3,46 @@ describe 'Extensions' do
3
3
  it 'pluralizes a normal word: dog' do
4
4
  Inflector.inflections.pluralize('dog').should == 'dogs'
5
5
  end
6
-
6
+
7
7
  it 'pluralizes words that end in "s": pass' do
8
8
  Inflector.inflections.pluralize('pass').should == 'passes'
9
9
  end
10
-
10
+
11
11
  it "pluralizes words that end in 'us'" do
12
12
  Inflector.inflections.pluralize('alumnus').should == 'alumni'
13
13
  end
14
-
14
+
15
15
  it "pluralizes words that end in 'ee'" do
16
16
  Inflector.inflections.pluralize('attendee').should == 'attendees'
17
17
  end
18
+
19
+ it "pluralizes words that end in 'e'" do
20
+ Inflector.inflections.pluralize('article').should == 'articles'
21
+ end
18
22
  end
19
-
23
+
20
24
  describe 'Singularization' do
21
25
  it 'singularizes a normal word: "dogs"' do
22
26
  Inflector.inflections.singularize('dogs').should == 'dog'
23
27
  end
24
-
28
+
25
29
  it "singualarizes a word that ends in 's': passes" do
26
30
  Inflector.inflections.singularize('passes').should == 'pass'
27
31
  end
28
-
32
+
29
33
  it "singualarizes a word that ends in 'ee': assignees" do
30
34
  Inflector.inflections.singularize('assignees').should == 'assignee'
31
35
  end
32
-
36
+
33
37
  it "singualarizes words that end in 'us'" do
34
38
  Inflector.inflections.singularize('alumni').should == 'alumnus'
35
39
  end
40
+
41
+ it "singualarizes words that end in 'es'" do
42
+ Inflector.inflections.singularize('articles').should == 'article'
43
+ end
36
44
  end
37
-
45
+
38
46
  describe 'Irregular Patterns' do
39
47
  it "handles person to people singularizing" do
40
48
  Inflector.inflections.singularize('people').should == 'person'
@@ -44,7 +52,7 @@ describe 'Extensions' do
44
52
  Inflector.inflections.pluralize('person').should == 'people'
45
53
  end
46
54
  end
47
-
55
+
48
56
  describe 'Adding Rules to Inflector' do
49
57
  it 'accepts new rules' do
50
58
  Inflector.inflections.irregular /^foot$/, 'feet'
@@ -102,3 +102,37 @@ describe "validations" do
102
102
 
103
103
  end
104
104
  end
105
+
106
+ class VTask
107
+ include MotionModel::Model
108
+ include MotionModel::Validatable
109
+
110
+ columns :name => :string
111
+ validate :name, :presence => true
112
+ end
113
+
114
+ describe "saving with validations" do
115
+
116
+ it "fails loudly" do
117
+ task = VTask.new
118
+ lambda { task.save!}.should.raise
119
+ end
120
+
121
+ it "can skip the validations" do
122
+ task = VTask.new
123
+ lambda { task.save({:validate => false})}.should.change { VTask.count }
124
+ end
125
+
126
+ it "should not save when validation fails" do
127
+ task = VTask.new
128
+ lambda { task.save }.should.not.change{ VTask.count }
129
+ task.save.should == false
130
+ end
131
+
132
+ it "saves it when everything is ok" do
133
+ task = VTask.new
134
+ task.name = "Save it"
135
+ lambda { task.save }.should.change { VTask.count }
136
+ end
137
+
138
+ end
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.3.6
4
+ version: 0.3.7
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: 2013-01-25 00:00:00.000000000 Z
12
+ date: 2013-02-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bubble-wrap