motion_model 0.5.4 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +61 -2
- data/motion/date_parser.rb +11 -0
- data/motion/model/model.rb +11 -2
- data/motion/model/model_casts.rb +38 -0
- data/motion/version.rb +1 -1
- data/spec/date_spec.rb +7 -0
- data/spec/model_casting_spec.rb +123 -2
- data/spec/model_spec.rb +91 -52
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df4d3bdf11fc759be47d10723d1f84ddae0e43fb
|
4
|
+
data.tar.gz: 2624cbb78924e42b3c2d1e008f2c4e75f2427598
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8e77f43b0997215ae37c366d2a5eb8f69c27cebe40bc5902dcd0c07c219d9a2e31120c0bb7da6859d530779f934c034e98819351696649dbb7c691d9c4dd6627
|
7
|
+
data.tar.gz: 45029f00583ddbb5e839d5ba173c0228d8e4ddad0bb87773c4adf92c761df3949ad79679fe0c9c21e4c30ff7ab46bb85454606584624efccc22b875ee8c9558a
|
data/README.md
CHANGED
@@ -10,7 +10,7 @@ data validation and actually quite a bit more.
|
|
10
10
|
File | Module | Description
|
11
11
|
---------------------|---------------------------|------------------------------------
|
12
12
|
**ext.rb** | N/A | Core Extensions that provide a few Rails-like niceties. Nothing new here, moving on...
|
13
|
-
**model.rb** | MotionModel::Model | You should read about it in "[What
|
13
|
+
**model.rb** | MotionModel::Model | You should read about it in "[What MotionModel Can Do](#what-motionmodel-can-do)". Model is the raison d'etre and the centerpiece of MotionModel.
|
14
14
|
**validatable.rb** | MotionModel::Validatable | Provides a basic validation framework for any arbitrary class. You can also create custom validations to suit your app's unique needs.
|
15
15
|
**input_helpers** | MotionModel::InputHelpers | Helps hook a collection up to a data form, populate the form, and retrieve the data afterwards. Note: *MotionModel supports Formotion for input handling as well as these input helpers*.
|
16
16
|
**formotion.rb** | MotionModel::Formotion | Provides an interface between MotionModel and Formotion
|
@@ -21,7 +21,7 @@ you like with it. See the LICENSE file in this project.
|
|
21
21
|
|
22
22
|
* [Getting Going](#getting-going)
|
23
23
|
* [Bugs, Features, and Issues, Oh My!](#bugs-features-and-issues-oh-my)
|
24
|
-
* [What
|
24
|
+
* [What MotionModel Can Do](#what-motionmodel-can-do)
|
25
25
|
* [Model Data Types](#model-data-types)
|
26
26
|
* [Validation Methods](#validation-methods)
|
27
27
|
* [Model Instances and Unique IDs](#model-instances-and-unique-ids)
|
@@ -274,6 +274,65 @@ Currently supported types are:
|
|
274
274
|
You are really not encouraged to stuff big things in your models, which is why a blob type
|
275
275
|
is not implemented. The smaller your data, the less overhead involved in saving/loading.
|
276
276
|
|
277
|
+
### User Defined Types
|
278
|
+
|
279
|
+
*New as of 0.6*: THIS FEATURE IS EXPERIMENTAL. You can use a class name instead of the
|
280
|
+
symbols to specify your types. Thus, you can choose more complex types in much the same
|
281
|
+
way as you might in a NOSQL database. Consider these examples:
|
282
|
+
|
283
|
+
```ruby
|
284
|
+
class Address
|
285
|
+
include MotionModel::Model
|
286
|
+
include MotionModel::ArrayModelAdapter
|
287
|
+
columns street: String,
|
288
|
+
city: String,
|
289
|
+
state: String,
|
290
|
+
zip: Integer
|
291
|
+
end
|
292
|
+
|
293
|
+
class Person
|
294
|
+
include MotionModel::Model
|
295
|
+
include MotionModel::ArrayModelAdapter
|
296
|
+
|
297
|
+
columns name: String,
|
298
|
+
address: Address
|
299
|
+
end
|
300
|
+
```
|
301
|
+
|
302
|
+
You can now use this `Person` class as follows:
|
303
|
+
|
304
|
+
```ruby
|
305
|
+
p = Person.find(:name).eq('Laurent').first
|
306
|
+
p.address.country = 'Belgium'
|
307
|
+
p.save
|
308
|
+
```
|
309
|
+
|
310
|
+
> **Note** Address includes all the MotionModel goodness. That is so that any class that embeds it can also persist it. You may be able to get away with defining `encodeWithCoder(coder)` and `initWithCoder(coder)` -- it's your choice, but not currently supported.
|
311
|
+
|
312
|
+
### Native Ruby Types
|
313
|
+
|
314
|
+
You can also use native Ruby types (limited to those supported by RubyMotion so no `DateTime`):
|
315
|
+
|
316
|
+
```ruby
|
317
|
+
class Person
|
318
|
+
include MotionModel::Model
|
319
|
+
include MotionModel::ArrayModelAdapter
|
320
|
+
|
321
|
+
columns name: String,
|
322
|
+
address: Hash
|
323
|
+
end
|
324
|
+
```
|
325
|
+
|
326
|
+
and you can use the class as follows:
|
327
|
+
|
328
|
+
```ruby
|
329
|
+
Person.create(name: 'Miss Piggy', address: {street: '111 Swine Ave', city: 'Muppetville', state: 'IN'})
|
330
|
+
```
|
331
|
+
|
332
|
+
etc.
|
333
|
+
|
334
|
+
> Special note: This is a bleeding edge feature as of 0.6, and because of the semantics of copying in Ruby, there are some deep copy situations that may cause objects to be copied or updated incompletely. These should create 100% reproducible bugs in your code so they should not sneak up on you. Nevertheless, understand that the guarantee is a shallow copy. Tests show that an array inside a class copies properly. YMMV.
|
335
|
+
|
277
336
|
### Special Columns
|
278
337
|
|
279
338
|
The two column names, `created_at` and `updated_at` will be adjusted automatically if they
|
data/motion/date_parser.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
module DateParser
|
2
2
|
@@isoDateFormatter = nil
|
3
|
+
@@detector = nil
|
3
4
|
|
4
5
|
# Parse a date string: E.g.:
|
5
6
|
#
|
@@ -58,6 +59,16 @@ module DateParser
|
|
58
59
|
date = @@isoDateFormatter.dateFromString date_string
|
59
60
|
return date
|
60
61
|
end
|
62
|
+
|
63
|
+
def self.allocate_data_detector
|
64
|
+
error = Pointer.new(:object)
|
65
|
+
@@detector = NSDataDetector.dataDetectorWithTypes(NSTextCheckingTypeDate, error:error)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.detector
|
69
|
+
allocate_data_detector if @@detector.nil?
|
70
|
+
return @@detector
|
71
|
+
end
|
61
72
|
end
|
62
73
|
|
63
74
|
|
data/motion/model/model.rb
CHANGED
@@ -317,7 +317,7 @@ module MotionModel
|
|
317
317
|
raise ArgumentError.new("you cannot use `description' as a column name because of a conflict with Cocoa.") if name.to_s == 'description'
|
318
318
|
|
319
319
|
case options
|
320
|
-
when Symbol, String
|
320
|
+
when Symbol, String, Class
|
321
321
|
add_field(name, options)
|
322
322
|
when Hash
|
323
323
|
add_field(name, options.delete(:type), options)
|
@@ -448,6 +448,10 @@ module MotionModel
|
|
448
448
|
"#{self.class.name}##{id}"
|
449
449
|
end
|
450
450
|
|
451
|
+
def motion_model?
|
452
|
+
true
|
453
|
+
end
|
454
|
+
|
451
455
|
def new_record?
|
452
456
|
@new_record
|
453
457
|
end
|
@@ -486,6 +490,11 @@ module MotionModel
|
|
486
490
|
@data[name]
|
487
491
|
end
|
488
492
|
|
493
|
+
def write_attribute(attr_name, value)
|
494
|
+
@data[attr_name] = value
|
495
|
+
@dirty = true
|
496
|
+
end
|
497
|
+
|
489
498
|
# Default to_i implementation returns value of id column, much as
|
490
499
|
# in Rails.
|
491
500
|
|
@@ -825,7 +834,7 @@ module MotionModel
|
|
825
834
|
when Symbol
|
826
835
|
{column => self.send(column)}
|
827
836
|
else
|
828
|
-
{column => default}
|
837
|
+
{column => (value.nil? ? default : value)}
|
829
838
|
end
|
830
839
|
end
|
831
840
|
|
data/motion/model/model_casts.rb
CHANGED
@@ -44,6 +44,42 @@ module MotionModel
|
|
44
44
|
String(arg)
|
45
45
|
end
|
46
46
|
|
47
|
+
def cast_to_arbitrary_class(arg, klass)
|
48
|
+
# This little oddity is because a number of built-in
|
49
|
+
# Ruby classes cannot be dup'ed. Not only that, they
|
50
|
+
# respond_to?(:dup) but raise an exception when you
|
51
|
+
# actually do it. Not only that, the behavior can be
|
52
|
+
# different depending on architecture (32- versus 64-bit).
|
53
|
+
#
|
54
|
+
# This is Ruby, folks, not just RubyMotion.
|
55
|
+
#
|
56
|
+
# We don't have to worry if it's a MotionModel, because
|
57
|
+
# using a reference to the data is ok. The by-reference
|
58
|
+
# copy is fine.
|
59
|
+
|
60
|
+
return arg if arg.respond_to?(:motion_model?)
|
61
|
+
|
62
|
+
# Another case is where there is a predefined cast rule
|
63
|
+
# on the type declared as a class level method motion_model_cast:
|
64
|
+
|
65
|
+
if klass.respond_to?(:motion_model_cast)
|
66
|
+
return klass.send(:motion_model_cast, arg)
|
67
|
+
end
|
68
|
+
|
69
|
+
# But if it is not a MotionModel, we either need to dup
|
70
|
+
# it (for most cases), or just assign it (for built-in
|
71
|
+
# types like Integer, Fixnum, Float, NilClass, etc.)
|
72
|
+
|
73
|
+
result = nil
|
74
|
+
begin
|
75
|
+
result = arg.dup
|
76
|
+
rescue
|
77
|
+
result = arg
|
78
|
+
end
|
79
|
+
|
80
|
+
result
|
81
|
+
end
|
82
|
+
|
47
83
|
def cast_to_type(column_name, arg) #nodoc
|
48
84
|
return nil if arg.nil? && ![ :boolean, :bool ].include?(column_type(column_name))
|
49
85
|
|
@@ -56,9 +92,11 @@ module MotionModel
|
|
56
92
|
when :text then cast_to_string(arg)
|
57
93
|
when :array then cast_to_array(arg)
|
58
94
|
when :hash then cast_to_hash(arg)
|
95
|
+
when Class then cast_to_arbitrary_class(arg, column_type(column_name))
|
59
96
|
else
|
60
97
|
raise ArgumentError.new("type #{column_name} : #{column_type(column_name)} is not possible to cast.")
|
61
98
|
end
|
62
99
|
end
|
63
100
|
end
|
64
101
|
end
|
102
|
+
|
data/motion/version.rb
CHANGED
data/spec/date_spec.rb
CHANGED
@@ -66,6 +66,13 @@ describe "time conversions" do
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
+
describe "date parser data detector reuse" do
|
70
|
+
it "creates a data detector if none is present" do
|
71
|
+
DateParser.class_variable_get(:@@detector).should.be.nil
|
72
|
+
DateParser.detector.class.should == NSDataDetector
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
69
76
|
describe "parsing ISO8601 date formats" do
|
70
77
|
class Model
|
71
78
|
include MotionModel::Model
|
data/spec/model_casting_spec.rb
CHANGED
@@ -22,7 +22,7 @@ describe 'Type casting' do
|
|
22
22
|
@convertible.a_date = '2012-09-15'
|
23
23
|
@convertible.an_array = 1..10
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
it 'does the type casting on instantiation' do
|
27
27
|
@convertible.a_boolean.should.is_a FalseClass
|
28
28
|
@convertible.an_int.should.is_a Integer
|
@@ -108,7 +108,7 @@ describe 'Type casting' do
|
|
108
108
|
it 'returns a NSDate for a date field' do
|
109
109
|
@convertible.a_date.should.is_a(NSDate)
|
110
110
|
end
|
111
|
-
|
111
|
+
|
112
112
|
it 'the date field should be the same as it was in string form' do
|
113
113
|
@convertible.a_date.to_s.should.match(/^2012-09-15/)
|
114
114
|
end
|
@@ -125,4 +125,125 @@ describe 'Type casting' do
|
|
125
125
|
it 'the array field should be the same as the range form' do
|
126
126
|
(@convertible.an_array.first..@convertible.an_array.last).should.equal(1..10)
|
127
127
|
end
|
128
|
+
|
129
|
+
describe 'can cast to an arbitrary type' do
|
130
|
+
class HasArbitraryTypes
|
131
|
+
include MotionModel::Model
|
132
|
+
include MotionModel::ArrayModelAdapter
|
133
|
+
columns name: String,
|
134
|
+
properties: Hash
|
135
|
+
end
|
136
|
+
|
137
|
+
class EmbeddedAddress
|
138
|
+
include MotionModel::Model
|
139
|
+
include MotionModel::ArrayModelAdapter
|
140
|
+
columns street: String,
|
141
|
+
city: String,
|
142
|
+
state: String,
|
143
|
+
zip: Integer,
|
144
|
+
pets: Array
|
145
|
+
# attr_accessor :street
|
146
|
+
# attr_accessor :city
|
147
|
+
# attr_accessor :state
|
148
|
+
# attr_accessor :zip
|
149
|
+
# attr_accessor :pets
|
150
|
+
|
151
|
+
# def initialize(options = {})
|
152
|
+
# @street = options[:street] if options[:street]
|
153
|
+
# @city = options[:city] if options[:city]
|
154
|
+
# @state = options[:state] if options[:state]
|
155
|
+
# @zip = options[:zip] if options[:zip]
|
156
|
+
# @pets = options[:pets] if options[:pets]
|
157
|
+
# end
|
158
|
+
end
|
159
|
+
|
160
|
+
class EmbeddingClass
|
161
|
+
include MotionModel::Model
|
162
|
+
include MotionModel::ArrayModelAdapter
|
163
|
+
columns name: String,
|
164
|
+
address: EmbeddedAddress,
|
165
|
+
pets: Array
|
166
|
+
end
|
167
|
+
|
168
|
+
before do
|
169
|
+
EmbeddingClass.delete_all
|
170
|
+
HasArbitraryTypes.delete_all
|
171
|
+
end
|
172
|
+
|
173
|
+
it "creation works" do
|
174
|
+
arb = HasArbitraryTypes.create(name: 'A Name', properties: {address: '123 Main Street', city: 'Seattle', state: 'WA'})
|
175
|
+
arb.name.should == 'A Name'
|
176
|
+
arb.properties.class.should == Hash
|
177
|
+
arb.properties[:address].should == '123 Main Street'
|
178
|
+
end
|
179
|
+
|
180
|
+
it "updating works" do
|
181
|
+
HasArbitraryTypes.create(name: 'Another Name', properties: {address: '123 Main Street', city: 'Seattle', state: 'WA'})
|
182
|
+
arb = HasArbitraryTypes.first
|
183
|
+
arb.properties[:address] = '234 Main Street'
|
184
|
+
arb.save
|
185
|
+
arb.properties[:address].should == '234 Main Street'
|
186
|
+
arb = HasArbitraryTypes.find(:name).eq('Another Name').first
|
187
|
+
arb.properties[:address].should == '234 Main Street'
|
188
|
+
end
|
189
|
+
|
190
|
+
it "creating objects with embedded documents works" do
|
191
|
+
addr = EmbeddedAddress.new(street: '2211 First', city: 'Seattle', state: 'WA', zip: 98104)
|
192
|
+
emb = EmbeddingClass.create(name: 'On Class', address: addr)
|
193
|
+
emb.address.class.should == EmbeddedAddress
|
194
|
+
emb.address.street.should == '2211 First'
|
195
|
+
end
|
196
|
+
|
197
|
+
it "copies embedded types" do
|
198
|
+
addr = EmbeddedAddress.new(street: '2211 First', city: 'Seattle', state: 'WA', zip: 98104, pets: ['rover', 'fido', 'barney'])
|
199
|
+
emb = EmbeddingClass.create(name: 'On Class', address: addr)
|
200
|
+
emb.address.pets.class.should == Array
|
201
|
+
emb.address.pets.should.include?('barney')
|
202
|
+
EmbeddingClass.first.address.pets.should.include?('barney')
|
203
|
+
end
|
204
|
+
|
205
|
+
it "updates embedded types" do
|
206
|
+
addr = EmbeddedAddress.new(street: '3322 First', city: 'Seattle', state: 'WA', zip: 98104, pets: ['rover', 'fido', 'barney'])
|
207
|
+
emb = EmbeddingClass.create(name: 'On Class', address: addr)
|
208
|
+
emb.address.pets.should.include?('barney')
|
209
|
+
found = EmbeddingClass.find(:name).eq('On Class').first
|
210
|
+
found.address.pets.should.include?('barney')
|
211
|
+
found.address.pets.delete('barney')
|
212
|
+
found.save
|
213
|
+
EmbeddingClass.find(:name).eq('On Class').first.address.pets.should.not.include?('barney')
|
214
|
+
end
|
215
|
+
|
216
|
+
it "serializes with arbitrary Ruby types without error" do
|
217
|
+
HasArbitraryTypes.create(name: 'A Name', properties: {address: '123 Main Street', city: 'Seattle', state: 'WA'})
|
218
|
+
lambda{HasArbitraryTypes.serialize_to_file('test.dat')}.should.not.raise
|
219
|
+
end
|
220
|
+
|
221
|
+
it "deserializes arbitrary Ruby types with correct values" do
|
222
|
+
HasArbitraryTypes.create(name: 'A Name', properties: {address: '123 Main Street', city: 'Seattle', state: 'WA'})
|
223
|
+
HasArbitraryTypes.serialize_to_file('test.dat')
|
224
|
+
HasArbitraryTypes.deserialize_from_file('test.dat')
|
225
|
+
result = HasArbitraryTypes.find(:name).eq('A Name').first
|
226
|
+
result.should.not.be.nil
|
227
|
+
result.properties.class.should == Hash
|
228
|
+
result.properties[:city].should == 'Seattle'
|
229
|
+
end
|
230
|
+
|
231
|
+
it "serializes arbitrary user-defined classes without error" do
|
232
|
+
addr = EmbeddedAddress.new(street: '2211 First', city: 'Seattle', state: 'WA', zip: 98104)
|
233
|
+
emb = EmbeddingClass.create(name: 'On Class', address: addr)
|
234
|
+
lambda{EmbeddingClass.serialize_to_file('test.dat')}.should.not.raise
|
235
|
+
end
|
236
|
+
|
237
|
+
it "deserializes arbitrary user-defined classes with correct values" do
|
238
|
+
addr = EmbeddedAddress.new(street: '2211 First', city: 'Seattle', state: 'WA', zip: 98104, pets: ['Katniss', 'Peeta'])
|
239
|
+
emb = EmbeddingClass.create(name: 'On Class', address: addr)
|
240
|
+
lambda{EmbeddingClass.serialize_to_file('test.dat')}.should.not.raise
|
241
|
+
EmbeddingClass.deserialize_from_file('test.dat')
|
242
|
+
result = EmbeddingClass.find(:name).eq('On Class').first
|
243
|
+
result.should.not.be.nil
|
244
|
+
result.address.class.should == EmbeddedAddress
|
245
|
+
result.address.city.should == 'Seattle'
|
246
|
+
result.address.pets.should.include?('Katniss')
|
247
|
+
end
|
248
|
+
end
|
128
249
|
end
|
data/spec/model_spec.rb
CHANGED
@@ -1,21 +1,31 @@
|
|
1
|
-
class
|
1
|
+
class ModelSpecTask
|
2
2
|
include MotionModel::Model
|
3
3
|
include MotionModel::ArrayModelAdapter
|
4
4
|
columns :name => :string,
|
5
5
|
:details => :string,
|
6
|
-
:some_day => :date
|
6
|
+
:some_day => :date,
|
7
|
+
:enabled => {:type => :boolean, :default => false}
|
7
8
|
|
8
9
|
def custom_attribute_by_method
|
9
10
|
"#{name} - #{details}"
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
13
|
-
class
|
14
|
+
class AModelSpecTask
|
14
15
|
include MotionModel::Model
|
15
16
|
include MotionModel::ArrayModelAdapter
|
16
17
|
columns :name, :details, :some_day
|
17
18
|
end
|
18
19
|
|
20
|
+
class BModelSpecTask
|
21
|
+
include MotionModel::Model
|
22
|
+
include MotionModel::ArrayModelAdapter
|
23
|
+
columns :name, :details
|
24
|
+
def details=(value)
|
25
|
+
write_attribute(:details, "overridden")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
19
29
|
class TypeCast
|
20
30
|
include MotionModel::Model
|
21
31
|
include MotionModel::ArrayModelAdapter
|
@@ -32,16 +42,16 @@ end
|
|
32
42
|
describe "Creating a model" do
|
33
43
|
describe 'column macro behavior' do
|
34
44
|
before do
|
35
|
-
|
45
|
+
ModelSpecTask.delete_all
|
36
46
|
end
|
37
47
|
|
38
48
|
it 'succeeds when creating a valid model from attributes' do
|
39
|
-
a_task =
|
49
|
+
a_task = ModelSpecTask.new(:name => 'name', :details => 'details')
|
40
50
|
a_task.name.should.equal('name')
|
41
51
|
end
|
42
52
|
|
43
53
|
it 'creates a model with all attributes even if some omitted' do
|
44
|
-
atask =
|
54
|
+
atask = ModelSpecTask.create(:name => 'bob')
|
45
55
|
atask.should.respond_to(:details)
|
46
56
|
end
|
47
57
|
|
@@ -49,175 +59,185 @@ describe "Creating a model" do
|
|
49
59
|
a_type_test = TypeCast.new
|
50
60
|
a_type_test.an_int.should.equal(3)
|
51
61
|
end
|
62
|
+
|
63
|
+
it "on initialization uses supplied value instead of default value, if supplied" do
|
64
|
+
a_task = ModelSpecTask.new(:enabled => true)
|
65
|
+
a_task.enabled.should.be.true
|
66
|
+
end
|
67
|
+
|
68
|
+
it "on creation uses supplied value instead of default value, if supplied" do
|
69
|
+
a_task = ModelSpecTask.create(:enabled => true)
|
70
|
+
a_task.enabled.should.be.true
|
71
|
+
end
|
52
72
|
|
53
73
|
it "can check for a column's existence on a model" do
|
54
|
-
|
74
|
+
ModelSpecTask.column?(:name).should.be.true
|
55
75
|
end
|
56
76
|
|
57
77
|
it "can check for a column's existence on an instance" do
|
58
|
-
a_task =
|
78
|
+
a_task = ModelSpecTask.new(:name => 'name', :details => 'details')
|
59
79
|
a_task.column?(:name).should.be.true
|
60
80
|
end
|
61
81
|
|
62
82
|
it "gets a list of columns on a model" do
|
63
|
-
cols =
|
83
|
+
cols = ModelSpecTask.columns
|
64
84
|
cols.should.include(:name)
|
65
85
|
cols.should.include(:details)
|
66
86
|
end
|
67
87
|
|
68
88
|
it "gets a list of columns on an instance" do
|
69
|
-
a_task =
|
89
|
+
a_task = ModelSpecTask.new
|
70
90
|
cols = a_task.columns
|
71
91
|
cols.should.include(:name)
|
72
92
|
cols.should.include(:details)
|
73
93
|
end
|
74
94
|
|
75
95
|
it "columns can be specified as a Hash" do
|
76
|
-
lambda{
|
77
|
-
|
96
|
+
lambda{ModelSpecTask.new}.should.not.raise
|
97
|
+
ModelSpecTask.new.column?(:name).should.be.true
|
78
98
|
end
|
79
99
|
|
80
100
|
it "columns can be specified as an Array" do
|
81
|
-
lambda{
|
82
|
-
|
101
|
+
lambda{AModelSpecTask.new}.should.not.raise
|
102
|
+
ModelSpecTask.new.column?(:name).should.be.true
|
83
103
|
end
|
84
104
|
|
85
105
|
it "the type of a column can be retrieved" do
|
86
|
-
|
106
|
+
ModelSpecTask.new.column_type(:some_day).should.equal(:date)
|
87
107
|
end
|
88
108
|
|
89
109
|
end
|
90
110
|
|
91
111
|
describe "ID handling" do
|
92
112
|
before do
|
93
|
-
|
113
|
+
ModelSpecTask.delete_all
|
94
114
|
end
|
95
115
|
|
96
116
|
|
97
117
|
it 'creates an id if none present' do
|
98
|
-
task =
|
118
|
+
task = ModelSpecTask.create
|
99
119
|
task.should.respond_to(:id)
|
100
120
|
end
|
101
121
|
|
102
122
|
it 'does not overwrite an existing ID' do
|
103
|
-
task =
|
123
|
+
task = ModelSpecTask.create(:id => 999)
|
104
124
|
task.id.should.equal(999)
|
105
125
|
end
|
106
126
|
|
107
127
|
it 'creates multiple objects with unique ids' do
|
108
|
-
|
128
|
+
ModelSpecTask.create.id.should.not.equal(ModelSpecTask.create.id)
|
109
129
|
end
|
110
130
|
|
111
131
|
end
|
112
132
|
|
113
133
|
describe 'count and length methods' do
|
114
134
|
before do
|
115
|
-
|
135
|
+
ModelSpecTask.delete_all
|
116
136
|
end
|
117
137
|
|
118
138
|
it 'has a length method' do
|
119
|
-
|
139
|
+
ModelSpecTask.should.respond_to(:length)
|
120
140
|
end
|
121
141
|
|
122
142
|
it 'has a count method' do
|
123
|
-
|
143
|
+
ModelSpecTask.should.respond_to(:count)
|
124
144
|
end
|
125
145
|
|
126
146
|
it 'when there is one element, length returns 1' do
|
127
|
-
task =
|
128
|
-
|
147
|
+
task = ModelSpecTask.create
|
148
|
+
ModelSpecTask.length.should.equal(1)
|
129
149
|
end
|
130
150
|
|
131
151
|
it 'when there is one element, count returns 1' do
|
132
|
-
task =
|
133
|
-
|
152
|
+
task = ModelSpecTask.create
|
153
|
+
ModelSpecTask.count.should.equal(1)
|
134
154
|
end
|
135
155
|
|
136
156
|
it 'instance variables have access to length and count' do
|
137
|
-
task =
|
157
|
+
task = ModelSpecTask.create
|
138
158
|
task.length.should.equal(1)
|
139
159
|
task.count.should.equal(1)
|
140
160
|
end
|
141
161
|
|
142
162
|
it 'when there is more than one element, length returned is correct' do
|
143
|
-
10.times {
|
144
|
-
|
163
|
+
10.times { ModelSpecTask.create }
|
164
|
+
ModelSpecTask.length.should.equal(10)
|
145
165
|
end
|
146
166
|
|
147
167
|
end
|
148
168
|
|
149
169
|
describe 'adding or updating' do
|
150
170
|
before do
|
151
|
-
|
171
|
+
ModelSpecTask.delete_all
|
152
172
|
end
|
153
173
|
|
154
174
|
it 'adds to the collection when a new task is saved' do
|
155
|
-
task =
|
156
|
-
lambda{task.save}.should.change{
|
175
|
+
task = ModelSpecTask.new
|
176
|
+
lambda{task.save}.should.change{ModelSpecTask.count}
|
157
177
|
end
|
158
178
|
|
159
179
|
it 'does not add to the collection when an existing task is saved' do
|
160
|
-
task =
|
180
|
+
task = ModelSpecTask.create(:name => 'updateable')
|
161
181
|
task.name = 'updated'
|
162
|
-
lambda{task.save}.should.not.change{
|
182
|
+
lambda{task.save}.should.not.change{ModelSpecTask.count}
|
163
183
|
end
|
164
184
|
|
165
185
|
it 'updates data properly' do
|
166
|
-
task =
|
186
|
+
task = ModelSpecTask.create(:name => 'updateable')
|
167
187
|
task.name = 'updated'
|
168
|
-
|
169
|
-
lambda{task.save}.should.change{
|
188
|
+
ModelSpecTask.where(:name).eq('updated').should == 0
|
189
|
+
lambda{task.save}.should.change{ModelSpecTask.where(:name).eq('updated')}
|
170
190
|
end
|
171
191
|
end
|
172
192
|
|
173
193
|
describe 'deleting' do
|
174
194
|
before do
|
175
|
-
|
176
|
-
|
177
|
-
1.upto(10) {|i|
|
195
|
+
ModelSpecTask.delete_all
|
196
|
+
ModelSpecTask.bulk_update do
|
197
|
+
1.upto(10) {|i| ModelSpecTask.create(:name => "task #{i}")}
|
178
198
|
end
|
179
199
|
end
|
180
200
|
|
181
201
|
it 'deletes a row' do
|
182
|
-
target =
|
202
|
+
target = ModelSpecTask.find(:name).eq('task 3').first
|
183
203
|
target.should.not == nil
|
184
204
|
target.delete
|
185
|
-
|
205
|
+
ModelSpecTask.find(:name).eq('task 3').count.should.equal 0
|
186
206
|
end
|
187
207
|
|
188
208
|
it 'deleting a row changes length' do
|
189
|
-
target =
|
190
|
-
lambda{target.delete}.should.change{
|
209
|
+
target = ModelSpecTask.find(:name).eq('task 2').first
|
210
|
+
lambda{target.delete}.should.change{ModelSpecTask.length}
|
191
211
|
end
|
192
212
|
|
193
213
|
it 'undeleting a row restores it' do
|
194
|
-
target =
|
214
|
+
target = ModelSpecTask.find(:name).eq('task 3').first
|
195
215
|
target.should.not == nil
|
196
216
|
target.delete
|
197
217
|
target.undelete
|
198
|
-
|
218
|
+
ModelSpecTask.find(:name).eq('task 3').count.should.equal 1
|
199
219
|
end
|
200
220
|
end
|
201
221
|
|
202
222
|
describe 'Handling Attribute Implementation' do
|
203
223
|
it 'raises a NoMethodError exception when an unknown attribute it referenced' do
|
204
|
-
task =
|
224
|
+
task = ModelSpecTask.new
|
205
225
|
lambda{task.bar}.should.raise(NoMethodError)
|
206
226
|
end
|
207
227
|
|
208
228
|
it 'raises a NoMethodError exception when an unknown attribute receives an assignment' do
|
209
|
-
task =
|
229
|
+
task = ModelSpecTask.new
|
210
230
|
lambda{task.bar = 'foo'}.should.raise(NoMethodError)
|
211
231
|
end
|
212
232
|
|
213
233
|
it 'successfully retrieves by attribute' do
|
214
|
-
task =
|
234
|
+
task = ModelSpecTask.create(:name => 'my task')
|
215
235
|
task.name.should == 'my task'
|
216
236
|
end
|
217
237
|
|
218
238
|
describe "dirty" do
|
219
239
|
before do
|
220
|
-
@new_task =
|
240
|
+
@new_task = ModelSpecTask.new
|
221
241
|
end
|
222
242
|
|
223
243
|
it 'marks a new object as dirty' do
|
@@ -246,8 +266,8 @@ describe "Creating a model" do
|
|
246
266
|
|
247
267
|
describe 'defining custom attributes' do
|
248
268
|
before do
|
249
|
-
|
250
|
-
@task =
|
269
|
+
ModelSpecTask.delete_all
|
270
|
+
@task = ModelSpecTask.create :name => 'Feed the Cat', :details => 'Get food, pour out'
|
251
271
|
end
|
252
272
|
|
253
273
|
it 'uses a custom attribute by method' do
|
@@ -255,6 +275,25 @@ describe "Creating a model" do
|
|
255
275
|
end
|
256
276
|
end
|
257
277
|
|
278
|
+
describe 'overloading accessors using write_attribute' do
|
279
|
+
before do
|
280
|
+
BModelSpecTask.delete_all
|
281
|
+
end
|
282
|
+
|
283
|
+
it 'updates the attribute on creation' do
|
284
|
+
@task = BModelSpecTask.create :name => 'foo', :details => 'bar'
|
285
|
+
@task.details.should.equal('overridden')
|
286
|
+
@task.should.not.be.dirty
|
287
|
+
end
|
288
|
+
|
289
|
+
it 'updates the attribute but does not save a new instance' do
|
290
|
+
@task = BModelSpecTask.new :name => 'foo', :details => 'bar'
|
291
|
+
@task.details.should.equal('overridden')
|
292
|
+
@task.should.be.dirty
|
293
|
+
end
|
294
|
+
|
295
|
+
end
|
296
|
+
|
258
297
|
describe 'protecting timestamps' do
|
259
298
|
class NoTimestamps
|
260
299
|
include MotionModel::Model
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: motion_model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Steve Ross
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-11-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bubble-wrap
|
@@ -105,7 +105,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
105
105
|
version: '0'
|
106
106
|
requirements: []
|
107
107
|
rubyforge_project:
|
108
|
-
rubygems_version: 2.
|
108
|
+
rubygems_version: 2.2.2
|
109
109
|
signing_key:
|
110
110
|
specification_version: 4
|
111
111
|
summary: Simple model and validation mixins for RubyMotion
|
@@ -127,3 +127,4 @@ test_files:
|
|
127
127
|
- spec/relation_spec.rb
|
128
128
|
- spec/transaction_spec.rb
|
129
129
|
- spec/validation_spec.rb
|
130
|
+
has_rdoc:
|