motion_model 0.3.6 → 0.3.7
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/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
|
-
[](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
|