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 +55 -47
- data/lib/motion_model/ext.rb +67 -11
- data/lib/motion_model/model/formotion.rb +10 -4
- data/lib/motion_model/model/model.rb +7 -3
- data/lib/motion_model/model/model_casts.rb +1 -1
- data/lib/motion_model/model/persistence.rb +2 -2
- data/lib/motion_model/validatable.rb +15 -0
- data/lib/motion_model/version.rb +1 -1
- data/spec/ext_spec.rb +17 -9
- data/spec/validation_spec.rb +34 -0
- metadata +2 -2
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
[![Code Climate](https://codeclimate.com/
|
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
|
data/lib/motion_model/ext.rb
CHANGED
@@ -64,31 +64,87 @@ class Inflector
|
|
64
64
|
# Put singular-form to plural form transformations here
|
65
65
|
@plurals = [
|
66
66
|
[/^person$/, 'people'],
|
67
|
-
[/^
|
67
|
+
[/^man$/, 'men'],
|
68
68
|
[/^child$/, 'children'],
|
69
|
-
[/^
|
70
|
-
[/^
|
71
|
-
[/^
|
72
|
-
[/^
|
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
|
-
[/^
|
99
|
+
[/^men$/, 'man'],
|
79
100
|
[/^children$/, 'child'],
|
80
|
-
[/^
|
81
|
-
[/^
|
82
|
-
[/^
|
83
|
-
[/^
|
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
|
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
|
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::
|
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::
|
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.
|
data/lib/motion_model/version.rb
CHANGED
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'
|
data/spec/validation_spec.rb
CHANGED
@@ -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.
|
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-
|
12
|
+
date: 2013-02-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bubble-wrap
|