motion_model 0.2.2 → 0.2.3
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 +79 -7
- data/lib/motion_model/ext.rb +32 -0
- data/lib/motion_model/input_helpers.rb +1 -4
- data/lib/motion_model/model/column.rb +22 -0
- data/lib/motion_model/{finder_query.rb → model/finder_query.rb} +11 -5
- data/lib/motion_model/{model.rb → model/model.rb} +23 -100
- data/lib/motion_model/model/persistence.rb +96 -0
- data/lib/motion_model/version.rb +1 -1
- data/spec/model_spec.rb +14 -22
- data/spec/persistence_spec.rb +84 -0
- metadata +8 -4
data/README.md
CHANGED
@@ -26,6 +26,34 @@ are:
|
|
26
26
|
helpers are certainly not the focus of this release, but
|
27
27
|
I am using these in an app to create Apple-like input forms in
|
28
28
|
static tables.
|
29
|
+
|
30
|
+
Getting Going
|
31
|
+
================
|
32
|
+
|
33
|
+
If you are using Bundler, put this in your Gemfile:
|
34
|
+
|
35
|
+
```
|
36
|
+
gem motion_model
|
37
|
+
```
|
38
|
+
|
39
|
+
then do:
|
40
|
+
|
41
|
+
```
|
42
|
+
bundle install
|
43
|
+
```
|
44
|
+
|
45
|
+
If you are not using Bundler:
|
46
|
+
|
47
|
+
```
|
48
|
+
gem install motion_model
|
49
|
+
```
|
50
|
+
|
51
|
+
then put this in your Rakefile after requiring `motion/project`:
|
52
|
+
|
53
|
+
```
|
54
|
+
require 'motion_model'
|
55
|
+
```
|
56
|
+
|
29
57
|
|
30
58
|
What Model Can Do
|
31
59
|
================
|
@@ -124,6 +152,15 @@ Things That Work
|
|
124
152
|
```ruby
|
125
153
|
@tasks = Task.find{|task| task.name =~ /dog/i && task.assigned_to == 'Bob'}
|
126
154
|
```
|
155
|
+
|
156
|
+
Note that finders always return a proxy (`FinderQuery`). You must use `first`, `last`, or `all`
|
157
|
+
to get useful results.
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
@tasks = Task.where(:owner).eq('jim') # => A FinderQuery.
|
161
|
+
@tasks.all # => An array of matching results.
|
162
|
+
@tasks.first # => The first result
|
163
|
+
```
|
127
164
|
|
128
165
|
You can perform ordering using either a field name or block syntax. Here's an example:
|
129
166
|
|
@@ -141,7 +178,7 @@ Things That Work
|
|
141
178
|
and of course on the "save" side:
|
142
179
|
|
143
180
|
```ruby
|
144
|
-
|
181
|
+
Task.serialize_to_file('tasks.dat')
|
145
182
|
end
|
146
183
|
```
|
147
184
|
|
@@ -150,11 +187,14 @@ Things That Work
|
|
150
187
|
protocol. When you declare your columns, `MotionModel` understands how to
|
151
188
|
serialize your data so you need take no further action.
|
152
189
|
|
153
|
-
* Relations, in principle work.
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
190
|
+
* Relations, in principle work. They are more embedded documents similar
|
191
|
+
to CouchDB or MongoDB. So instead of being separate tables, the embedded
|
192
|
+
documents are model objects contained in a collection.
|
193
|
+
|
194
|
+
**Relations Are Untested**. This is completely experimental, but to use
|
195
|
+
them, just define a column as type `:array`. Initializing these properly
|
196
|
+
and testing them is a high priority for me, so expect it to be addressed
|
197
|
+
soon.
|
158
198
|
|
159
199
|
* Core extensions work. The following are supplied:
|
160
200
|
|
@@ -165,6 +205,15 @@ Things That Work
|
|
165
205
|
- Array#empty?
|
166
206
|
- Hash#empty?
|
167
207
|
- Symbol#titleize
|
208
|
+
|
209
|
+
Also in the extensions is a debug class to log stuff to the console.
|
210
|
+
This may be preferable to `puts` just because it's easier to spot in
|
211
|
+
your code and it gives you the exact level and file/line number of the
|
212
|
+
info/warning/error in your console output:
|
213
|
+
|
214
|
+
- Debug.info(message)
|
215
|
+
- Debug.warning(message)
|
216
|
+
- Debug.error(message)
|
168
217
|
|
169
218
|
Things In The Pipeline
|
170
219
|
----------------------
|
@@ -178,5 +227,28 @@ Things In The Pipeline
|
|
178
227
|
Problems/Comments
|
179
228
|
------------------
|
180
229
|
|
181
|
-
Please raise an issue if you find something that doesn't work, some
|
230
|
+
Please **raise an issue** on GitHub if you find something that doesn't work, some
|
182
231
|
syntax that smells, etc.
|
232
|
+
|
233
|
+
If you want to stay on the bleeding edge, clone yourself a copy (or better yet, fork
|
234
|
+
one).
|
235
|
+
|
236
|
+
Then be sure references to motion_model are commented out or removed from your Gemfile
|
237
|
+
and/or Rakefile and put this in your Rakefile:
|
238
|
+
|
239
|
+
```
|
240
|
+
require "~/github/local//MotionModel/lib/motion_model.rb"
|
241
|
+
```
|
242
|
+
|
243
|
+
The `~/github/local` is where I cloned it, but you can put it anyplace. Next, make
|
244
|
+
sure you are following the project on GitHub so you know when there are changes.
|
245
|
+
|
246
|
+
Submissions/Patches
|
247
|
+
------------------
|
248
|
+
|
249
|
+
Obviously, the ideal one is a pull request from your own fork, complete with passing
|
250
|
+
specs.
|
251
|
+
|
252
|
+
Really, even a failing spec or some proposed code is fine. I really want to make
|
253
|
+
this a decent tool for RubyMotion developers who need a straightforward data
|
254
|
+
modeling and persistence framework.
|
data/lib/motion_model/ext.rb
CHANGED
@@ -35,3 +35,35 @@ class Symbol
|
|
35
35
|
self.to_s.titleize
|
36
36
|
end
|
37
37
|
end
|
38
|
+
|
39
|
+
class Debug
|
40
|
+
@@silent = false
|
41
|
+
|
42
|
+
# Use silence if you want to keep messages from being echoed
|
43
|
+
# to the console.
|
44
|
+
def self.silence
|
45
|
+
@@silent = true
|
46
|
+
end
|
47
|
+
|
48
|
+
# Use resume when you want messages that were silenced to
|
49
|
+
# resume displaying.
|
50
|
+
def self.resume
|
51
|
+
@@silent = false
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.put_message(type, message)
|
55
|
+
puts("#{type} #{caller[1]}: #{message}") unless @@silent
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.info(msg)
|
59
|
+
put_message 'INFO', msg
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.warning(msg)
|
63
|
+
put_message 'WARNING', msg
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.error(msg)
|
67
|
+
put_message 'ERROR', msg
|
68
|
+
end
|
69
|
+
end
|
@@ -37,7 +37,6 @@ module MotionModel
|
|
37
37
|
# Only one field mapping may be supplied for
|
38
38
|
# a given class.
|
39
39
|
def field(field, options = {})
|
40
|
-
puts "adding field #{field}"
|
41
40
|
label = options[:label] || field.humanize
|
42
41
|
@binding_data << FieldBindingMap.new(:label => label, :name => field)
|
43
42
|
end
|
@@ -110,10 +109,8 @@ module MotionModel
|
|
110
109
|
raise ModelNotSetError.new("You must set the model before binding it.") unless @model
|
111
110
|
|
112
111
|
fields do |field|
|
113
|
-
puts "*** retrieving data for #{field.name} and tag #{field.tag} ***"
|
114
112
|
view_obj = self.view.viewWithTag(field.tag)
|
115
|
-
|
116
|
-
@model.send("#{field.name}=".to_sym, view_obj.text)
|
113
|
+
@model.send("#{field.name}=".to_sym, view_obj.text) if view_obj.respond_to?(:text)
|
117
114
|
end
|
118
115
|
end
|
119
116
|
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module MotionModel
|
2
|
+
module Model
|
3
|
+
class Column
|
4
|
+
attr_accessor :name
|
5
|
+
attr_accessor :type
|
6
|
+
attr_accessor :default
|
7
|
+
|
8
|
+
def initialize(name = nil, type = nil, default = nil)
|
9
|
+
@name = name
|
10
|
+
@type = type
|
11
|
+
@default = default || nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_attr(name, type, default = nil)
|
15
|
+
@name = name
|
16
|
+
@type = type
|
17
|
+
@default = default || nil
|
18
|
+
end
|
19
|
+
alias_method :add_attribute, :add_attr
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -23,12 +23,18 @@ module MotionModel
|
|
23
23
|
end
|
24
24
|
|
25
25
|
######## relational methods ########
|
26
|
-
def
|
27
|
-
|
28
|
-
|
26
|
+
def translate_case(item, case_sensitive)#nodoc
|
27
|
+
item = item.downcase if case_sensitive === false && item.respond_to?(:downcase)
|
28
|
+
item
|
29
|
+
end
|
30
|
+
|
31
|
+
def do_comparison(query_string, options = {:case_sensitive => false})#nodoc
|
32
|
+
query_string = translate_case(query_string, options[:case_sensitive])
|
33
|
+
@collection = @collection.collect do |item|
|
29
34
|
comparator = item.send(@field_name.to_sym)
|
30
|
-
|
31
|
-
|
35
|
+
comparator = translate_case(comparator, options[:case_sensitive])
|
36
|
+
item if yield query_string, comparator
|
37
|
+
end.compact
|
32
38
|
self
|
33
39
|
end
|
34
40
|
|
@@ -41,25 +41,6 @@ module MotionModel
|
|
41
41
|
class PersistFileError < Exception; end
|
42
42
|
|
43
43
|
module Model
|
44
|
-
class Column
|
45
|
-
attr_accessor :name
|
46
|
-
attr_accessor :type
|
47
|
-
attr_accessor :default
|
48
|
-
|
49
|
-
def initialize(name = nil, type = nil, default = nil)
|
50
|
-
@name = name
|
51
|
-
@type = type
|
52
|
-
@default = default || nil
|
53
|
-
end
|
54
|
-
|
55
|
-
def add_attr(name, type, default = nil)
|
56
|
-
@name = name
|
57
|
-
@type = type
|
58
|
-
@default = default || nil
|
59
|
-
end
|
60
|
-
alias_method :add_attribute, :add_attr
|
61
|
-
end
|
62
|
-
|
63
44
|
def self.included(base)
|
64
45
|
base.extend(ClassMethods)
|
65
46
|
base.instance_variable_set("@_columns", [])
|
@@ -98,6 +79,8 @@ module MotionModel
|
|
98
79
|
case fields.first
|
99
80
|
when Hash
|
100
81
|
fields.first.each_pair do |name, options|
|
82
|
+
raise ArgumentError.new("you cannot use `description' as a column name because of a conflict with Cocoa.") if name.to_s == 'description'
|
83
|
+
|
101
84
|
case options
|
102
85
|
when Symbol, String
|
103
86
|
add_field(name, options)
|
@@ -178,6 +161,7 @@ module MotionModel
|
|
178
161
|
# Empties the entire store.
|
179
162
|
def delete_all
|
180
163
|
@collection = [] # TODO: Handle cascading or let GC take care of it.
|
164
|
+
@collection.compact!
|
181
165
|
end
|
182
166
|
|
183
167
|
# Finds row(s) within the data store. E.g.,
|
@@ -227,37 +211,9 @@ module MotionModel
|
|
227
211
|
@collection.each{|item| yield item}
|
228
212
|
end
|
229
213
|
|
230
|
-
|
231
|
-
|
232
|
-
# Note that subsequent calls to serialize/deserialize methods
|
233
|
-
# will remember the file name, so they may omit that argument.
|
234
|
-
#
|
235
|
-
# Raises a +MotionModel::PersistFileFailureError+ on failure.
|
236
|
-
def deserialize_from_file(file_name = nil)
|
237
|
-
@file_name ||= file_name
|
238
|
-
new_object = self.new
|
239
|
-
|
240
|
-
if File.exist? documents_file(@file_name)
|
241
|
-
error_ptr = Pointer.new(:object)
|
242
|
-
|
243
|
-
data = NSData.dataWithContentsOfFile(documents_file(file_name), options:NSDataReadingMappedIfSafe, error:error_ptr)
|
244
|
-
|
245
|
-
if data.nil?
|
246
|
-
error = error_ptr[0]
|
247
|
-
raise MotionModel::PersistFileFailureError.new "Error when reading the data: #{error}"
|
248
|
-
else
|
249
|
-
return NSKeyedUnarchiver.unarchiveObjectWithData(data)
|
250
|
-
end
|
251
|
-
else
|
252
|
-
return false
|
253
|
-
end
|
214
|
+
def empty?
|
215
|
+
@collection.empty?
|
254
216
|
end
|
255
|
-
|
256
|
-
def documents_file(file_name)
|
257
|
-
file_path = File.join NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true), file_name
|
258
|
-
file_path
|
259
|
-
end
|
260
|
-
|
261
217
|
end
|
262
218
|
|
263
219
|
####### Instance Methods #######
|
@@ -282,6 +238,8 @@ module MotionModel
|
|
282
238
|
cast_value = cast_to_type(col, options[col])
|
283
239
|
@data[col] = cast_value
|
284
240
|
end
|
241
|
+
|
242
|
+
dirty = true
|
285
243
|
end
|
286
244
|
|
287
245
|
def cast_to_type(column_name, arg)
|
@@ -298,8 +256,7 @@ module MotionModel
|
|
298
256
|
return_value = arg.is_a?(Float) ? arg : arg.to_f
|
299
257
|
when :date
|
300
258
|
return arg if arg.is_a?(NSDate)
|
301
|
-
|
302
|
-
return_value = @cached_date_formatter.dateFromString(date_string)
|
259
|
+
return_value = NSDate.dateWithNaturalLanguageString(arg, locale:NSUserDefaults.standardUserDefaults.dictionaryRepresentation)
|
303
260
|
else
|
304
261
|
raise ArgumentError.new("type #{column_name} : #{type(column_name)} is not possible to cast.")
|
305
262
|
end
|
@@ -311,7 +268,16 @@ module MotionModel
|
|
311
268
|
end
|
312
269
|
|
313
270
|
def save
|
314
|
-
self.class.instance_variable_get('@collection')
|
271
|
+
collection = self.class.instance_variable_get('@collection')
|
272
|
+
@dirty = false
|
273
|
+
|
274
|
+
# Existing object implies update in place
|
275
|
+
# TODO: Optimize location of existing id
|
276
|
+
if obj = collection.find{|o| o.id == @data[:id]}
|
277
|
+
obj = self
|
278
|
+
else
|
279
|
+
collection << self
|
280
|
+
end
|
315
281
|
end
|
316
282
|
|
317
283
|
def delete
|
@@ -341,32 +307,6 @@ module MotionModel
|
|
341
307
|
def type(field_name)
|
342
308
|
self.class.type(field_name)
|
343
309
|
end
|
344
|
-
|
345
|
-
def initWithCoder(coder)
|
346
|
-
self.init
|
347
|
-
columns.each do |attr|
|
348
|
-
# If a model revision has taken place, don't try to decode
|
349
|
-
# something that's not there.
|
350
|
-
new_tag_id = 1
|
351
|
-
if coder.containsValueForKey(attr.to_s)
|
352
|
-
value = coder.decodeObjectForKey(attr.to_s)
|
353
|
-
self.instance_variable_set('@' + attr.to_s, value || '')
|
354
|
-
else
|
355
|
-
self.instance_variable_set('@' + attr.to_s, '') # set to empty string if new attribute
|
356
|
-
end
|
357
|
-
|
358
|
-
# re-issue tags to make sure they are unique
|
359
|
-
@tag = new_tag_id
|
360
|
-
new_tag_id += 1
|
361
|
-
end
|
362
|
-
self
|
363
|
-
end
|
364
|
-
|
365
|
-
def encodeWithCoder(coder)
|
366
|
-
columns.each do |attr|
|
367
|
-
coder.encodeObject(self.send(attr), forKey: attr.to_s)
|
368
|
-
end
|
369
|
-
end
|
370
310
|
|
371
311
|
# Modify respond_to? to add model's attributes.
|
372
312
|
alias_method :old_respond_to?, :respond_to?
|
@@ -374,6 +314,10 @@ module MotionModel
|
|
374
314
|
column_named(method) || old_respond_to?(method)
|
375
315
|
end
|
376
316
|
|
317
|
+
def dirty?
|
318
|
+
@dirty
|
319
|
+
end
|
320
|
+
|
377
321
|
# Handle attribute retrieval
|
378
322
|
#
|
379
323
|
# Gets and sets work as expected, and type casting occurs
|
@@ -393,6 +337,7 @@ module MotionModel
|
|
393
337
|
|
394
338
|
if col
|
395
339
|
if method.to_s.include?('=')
|
340
|
+
@dirty = true
|
396
341
|
return @data[base_method] = self.cast_to_type(base_method, args[0])
|
397
342
|
else
|
398
343
|
return @data[base_method]
|
@@ -406,28 +351,6 @@ ERRORINFO
|
|
406
351
|
end
|
407
352
|
end
|
408
353
|
|
409
|
-
# Serializes data to a persistent store (file, in this
|
410
|
-
# terminology). Serialization is synchronous, so this
|
411
|
-
# will pause your run loop until complete.
|
412
|
-
#
|
413
|
-
# +file_name+ is the name of the persistent store you
|
414
|
-
# want to use. If you omit this, it will use the last
|
415
|
-
# remembered file name.
|
416
|
-
#
|
417
|
-
# Raises a +MotionModel::PersistFileFailureError+ on failure.
|
418
|
-
def serialize_to_file(file_name = nil)
|
419
|
-
@file_name ||= file_name
|
420
|
-
error_ptr = Pointer.new(:object)
|
421
|
-
|
422
|
-
data = NSKeyedArchiver.archivedDataWithRootObject self
|
423
|
-
unless data.writeToFile(self.class.documents_file(file_name), options: NSDataWritingAtomic, error: error_ptr)
|
424
|
-
# De-reference the pointer.
|
425
|
-
error = error_ptr[0]
|
426
|
-
|
427
|
-
# Now we can use the `error' object.
|
428
|
-
raise MotionModel::PersistFileFailureError.new "Error when writing data: #{error}"
|
429
|
-
end
|
430
|
-
end
|
431
354
|
end
|
432
355
|
end
|
433
356
|
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module MotionModel
|
2
|
+
class PersistFileError < Exception; end
|
3
|
+
|
4
|
+
module Model
|
5
|
+
module ClassMethods
|
6
|
+
# Returns the unarchived object if successful, otherwise false
|
7
|
+
#
|
8
|
+
# Note that subsequent calls to serialize/deserialize methods
|
9
|
+
# will remember the file name, so they may omit that argument.
|
10
|
+
#
|
11
|
+
# Raises a +MotionModel::PersistFileFailureError+ on failure.
|
12
|
+
def deserialize_from_file(file_name = nil)
|
13
|
+
@file_name ||= file_name
|
14
|
+
|
15
|
+
if File.exist? documents_file(@file_name)
|
16
|
+
error_ptr = Pointer.new(:object)
|
17
|
+
|
18
|
+
data = NSData.dataWithContentsOfFile(documents_file(file_name), options:NSDataReadingMappedIfSafe, error:error_ptr)
|
19
|
+
|
20
|
+
if data.nil?
|
21
|
+
error = error_ptr[0]
|
22
|
+
raise MotionModel::PersistFileFailureError.new "Error when reading the data: #{error}"
|
23
|
+
else
|
24
|
+
collection = NSKeyedUnarchiver.unarchiveObjectWithData(data)
|
25
|
+
return self
|
26
|
+
end
|
27
|
+
else
|
28
|
+
return false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
# Serializes data to a persistent store (file, in this
|
32
|
+
# terminology). Serialization is synchronous, so this
|
33
|
+
# will pause your run loop until complete.
|
34
|
+
#
|
35
|
+
# +file_name+ is the name of the persistent store you
|
36
|
+
# want to use. If you omit this, it will use the last
|
37
|
+
# remembered file name.
|
38
|
+
#
|
39
|
+
# Raises a +MotionModel::PersistFileFailureError+ on failure.
|
40
|
+
def serialize_to_file(file_name = nil)
|
41
|
+
@file_name ||= file_name
|
42
|
+
error_ptr = Pointer.new(:object)
|
43
|
+
|
44
|
+
data = NSKeyedArchiver.archivedDataWithRootObject @collection
|
45
|
+
unless data.writeToFile(documents_file(file_name), options: NSDataWritingAtomic, error: error_ptr)
|
46
|
+
# De-reference the pointer.
|
47
|
+
error = error_ptr[0]
|
48
|
+
|
49
|
+
# Now we can use the `error' object.
|
50
|
+
raise MotionModel::PersistFileFailureError.new "Error when writing data: #{error}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
def documents_file(file_name)
|
56
|
+
file_path = File.join NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true), file_name
|
57
|
+
file_path
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def initWithCoder(coder)
|
62
|
+
self.init
|
63
|
+
|
64
|
+
new_tag_id = 1
|
65
|
+
columns.each do |attr|
|
66
|
+
# If a model revision has taken place, don't try to decode
|
67
|
+
# something that's not there.
|
68
|
+
if coder.containsValueForKey(attr.to_s)
|
69
|
+
value = coder.decodeObjectForKey(attr.to_s)
|
70
|
+
self.send("#{attr}=", value)
|
71
|
+
else
|
72
|
+
self.send("#{attr}=", nil)
|
73
|
+
end
|
74
|
+
|
75
|
+
# re-issue tags to make sure they are unique
|
76
|
+
@tag = new_tag_id
|
77
|
+
new_tag_id += 1
|
78
|
+
end
|
79
|
+
save
|
80
|
+
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
# Follow Apple's recommendation not to encode missing
|
85
|
+
# values.
|
86
|
+
def encodeWithCoder(coder)
|
87
|
+
columns.each do |attr|
|
88
|
+
value = self.send(attr)
|
89
|
+
unless value.nil?
|
90
|
+
coder.encodeObject(value, forKey: attr.to_s)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
data/lib/motion_model/version.rb
CHANGED
data/spec/model_spec.rb
CHANGED
@@ -133,12 +133,13 @@ describe "Creating a model" do
|
|
133
133
|
|
134
134
|
describe 'finders' do
|
135
135
|
before do
|
136
|
+
Task.delete_all
|
136
137
|
10.times {|i| Task.create(:name => "task #{i}")}
|
137
138
|
end
|
138
139
|
|
139
140
|
describe 'find' do
|
140
141
|
it 'finds elements within the collection' do
|
141
|
-
Task.find(3).name.should.equal('task 3')
|
142
|
+
task = Task.find(3).name.should.equal('task 3')
|
142
143
|
end
|
143
144
|
|
144
145
|
it 'returns nil if find by id is not found' do
|
@@ -147,7 +148,15 @@ describe "Creating a model" do
|
|
147
148
|
|
148
149
|
it 'looks into fields if field name supplied' do
|
149
150
|
Task.create(:name => 'find me')
|
150
|
-
Task.find(:name).eq('find me')
|
151
|
+
tasks = Task.find(:name).eq('find me')
|
152
|
+
tasks.all.length.should.equal(1)
|
153
|
+
tasks.first.name.should == 'find me'
|
154
|
+
end
|
155
|
+
|
156
|
+
it "provides an array of valid model instances when doing a find" do
|
157
|
+
Task.create(:name => 'find me')
|
158
|
+
tasks = Task.find(:name).eq('find me')
|
159
|
+
tasks.first.name.should.eql 'find me'
|
151
160
|
end
|
152
161
|
|
153
162
|
it 'allows for multiple (chained) query parameters' do
|
@@ -168,12 +177,12 @@ describe "Creating a model" do
|
|
168
177
|
|
169
178
|
it 'using where instead of find' do
|
170
179
|
atask = Task.create(:name => 'find me', :details => "details 1")
|
171
|
-
found_task = Task.where(:details).contain("
|
180
|
+
found_task = Task.where(:details).contain("s 1").first.details.should == 'details 1'
|
172
181
|
end
|
173
182
|
|
174
183
|
it 'handles case-sensitive queries' do
|
175
184
|
task = Task.create :name => 'Bob'
|
176
|
-
Task.find(:name).eq('bob', :case_sensitive => true).all.should
|
185
|
+
Task.find(:name).eq('bob', :case_sensitive => true).all.length.should == 0
|
177
186
|
end
|
178
187
|
|
179
188
|
it 'all returns all members of the collection as an array' do
|
@@ -248,7 +257,7 @@ describe "Creating a model" do
|
|
248
257
|
it 'deletes a row' do
|
249
258
|
target = Task.find(:name).eq('task 3').first
|
250
259
|
target.delete
|
251
|
-
Task.find(:description).eq('Task 3').should
|
260
|
+
Task.find(:description).eq('Task 3').length.should.equal 0
|
252
261
|
end
|
253
262
|
|
254
263
|
it 'deleting a row changes length' do
|
@@ -327,20 +336,3 @@ describe "Creating a model" do
|
|
327
336
|
end
|
328
337
|
end
|
329
338
|
|
330
|
-
describe 'persistence' do
|
331
|
-
before do
|
332
|
-
Task.delete_all
|
333
|
-
%w(one two three).each do |task|
|
334
|
-
@tasks = Task.create(:name => "name #{task}")
|
335
|
-
end
|
336
|
-
@tasks.serialize_to_file('test.dat')
|
337
|
-
end
|
338
|
-
|
339
|
-
it 'reads persisted model data' do
|
340
|
-
tasks = Task.deserialize_from_file('test.dat')
|
341
|
-
|
342
|
-
Task.first.name.should == 'name one'
|
343
|
-
Task.last.name.should == 'name three'
|
344
|
-
Task.count.should == 3
|
345
|
-
end
|
346
|
-
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
class PersistTask
|
2
|
+
include MotionModel::Model
|
3
|
+
columns :name, :desc
|
4
|
+
end
|
5
|
+
|
6
|
+
describe 'persistence' do
|
7
|
+
before do
|
8
|
+
PersistTask.delete_all
|
9
|
+
%w(one two three).each do |task|
|
10
|
+
@tasks = PersistTask.create(:name => "name #{task}")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it "serializes data" do
|
15
|
+
lambda{PersistTask.serialize_to_file('test.dat')}.should.not.raise
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'reads persisted model data' do
|
19
|
+
PersistTask.serialize_to_file('test.dat')
|
20
|
+
|
21
|
+
PersistTask.delete_all
|
22
|
+
|
23
|
+
PersistTask.count.should == 0
|
24
|
+
|
25
|
+
tasks = PersistTask.deserialize_from_file('test.dat')
|
26
|
+
|
27
|
+
PersistTask.count.should == 3
|
28
|
+
PersistTask.first.name.should == 'name one'
|
29
|
+
PersistTask.last.name.should == 'name three'
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'model change resiliency' do
|
33
|
+
it 'column addition' do
|
34
|
+
class Foo
|
35
|
+
include MotionModel::Model
|
36
|
+
columns :name => :string
|
37
|
+
end
|
38
|
+
@foo = Foo.create(:name=> 'Bob')
|
39
|
+
Foo.serialize_to_file('test.dat')
|
40
|
+
|
41
|
+
@foo.should.not.respond_to :address
|
42
|
+
|
43
|
+
class Foo
|
44
|
+
include MotionModel::Model
|
45
|
+
columns :name => :string,
|
46
|
+
:address => :string
|
47
|
+
end
|
48
|
+
Foo.deserialize_from_file('test.dat')
|
49
|
+
|
50
|
+
@foo = Foo.first
|
51
|
+
|
52
|
+
@foo.name.should == 'Bob'
|
53
|
+
@foo.address.should == nil
|
54
|
+
@foo.should.respond_to :address
|
55
|
+
Foo.length.should == 1
|
56
|
+
end
|
57
|
+
|
58
|
+
it "column removal" do
|
59
|
+
class Foo
|
60
|
+
include MotionModel::Model
|
61
|
+
columns :name => :string, :desc => :string
|
62
|
+
end
|
63
|
+
@foo = Foo.create(:name=> 'Bob', :desc => 'who cares anyway?')
|
64
|
+
Foo.serialize_to_file('test.dat')
|
65
|
+
|
66
|
+
@foo.should.respond_to :desc
|
67
|
+
|
68
|
+
class Foo
|
69
|
+
include MotionModel::Model
|
70
|
+
columns :name => :string,
|
71
|
+
:address => :string
|
72
|
+
end
|
73
|
+
Foo.deserialize_from_file('test.dat')
|
74
|
+
|
75
|
+
@foo = Foo.first
|
76
|
+
|
77
|
+
@foo.name.should == 'Bob'
|
78
|
+
@foo.address.should == nil
|
79
|
+
@foo.should.not.respond_to :desc
|
80
|
+
@foo.should.respond_to :address
|
81
|
+
Foo.length.should == 1
|
82
|
+
end
|
83
|
+
end
|
84
|
+
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.2.
|
4
|
+
version: 0.2.3
|
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-09-
|
12
|
+
date: 2012-09-13 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Simple model and validation mixins for RubyMotion
|
15
15
|
email:
|
@@ -25,13 +25,16 @@ files:
|
|
25
25
|
- app/app_delegate.rb
|
26
26
|
- lib/motion_model.rb
|
27
27
|
- lib/motion_model/ext.rb
|
28
|
-
- lib/motion_model/finder_query.rb
|
29
28
|
- lib/motion_model/input_helpers.rb
|
30
|
-
- lib/motion_model/model.rb
|
29
|
+
- lib/motion_model/model/column.rb
|
30
|
+
- lib/motion_model/model/finder_query.rb
|
31
|
+
- lib/motion_model/model/model.rb
|
32
|
+
- lib/motion_model/model/persistence.rb
|
31
33
|
- lib/motion_model/validatable.rb
|
32
34
|
- lib/motion_model/version.rb
|
33
35
|
- motion_model.gemspec
|
34
36
|
- spec/model_spec.rb
|
37
|
+
- spec/persistence_spec.rb
|
35
38
|
homepage: https://github.com/sxross/MotionModel
|
36
39
|
licenses: []
|
37
40
|
post_install_message:
|
@@ -58,3 +61,4 @@ specification_version: 3
|
|
58
61
|
summary: Simple model and validation mixins for RubyMotion
|
59
62
|
test_files:
|
60
63
|
- spec/model_spec.rb
|
64
|
+
- spec/persistence_spec.rb
|