motion_model 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +6 -0
- data/lib/motion_model/adapters/array_model_adapter.rb +9 -0
- data/lib/motion_model/ext.rb +51 -33
- data/lib/motion_model/model/model.rb +7 -8
- data/lib/motion_model/version.rb +1 -1
- data/motion_model.gemspec +1 -0
- metadata +18 -5
- data/lib/motion_model/model/persistence.rb +0 -102
- data/spec/persistence_spec.rb +0 -187
data/CHANGELOG
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
2102-03-15: Fixed bug where created_at and updated_at were being incorrectly
|
2
|
+
when restored from persistence (thanks Justin McPherson for finding
|
3
|
+
that).
|
4
|
+
|
5
|
+
Moved all NSCoder stuff out of Model to ArrayModelAdapter.
|
6
|
+
|
1
7
|
2012-02-19: Included Doug Puchalski's great refactoring of Model that provides an
|
2
8
|
adapter for ArrayModelAdapter. WARNING!!! This is a breaking change
|
3
9
|
since version 0.3.8. You will have to include:
|
@@ -107,6 +107,15 @@ module MotionModel
|
|
107
107
|
self.class.issue_notification(self, :action => 'add')
|
108
108
|
end
|
109
109
|
|
110
|
+
# This adds to the ArrayStore without the magic date
|
111
|
+
# and id manipulation stuff
|
112
|
+
def add_to_store(*)
|
113
|
+
do_insert
|
114
|
+
@dirty = @new_record = false
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
|
110
119
|
# Count of objects in the current collection
|
111
120
|
def length
|
112
121
|
collection.length
|
data/lib/motion_model/ext.rb
CHANGED
@@ -1,49 +1,67 @@
|
|
1
|
+
# The reason the String extensions are wrapped in
|
2
|
+
# conditional blocks is to reduce the likelihood
|
3
|
+
# of a namespace collision with other libraries.
|
4
|
+
|
1
5
|
class String
|
2
|
-
|
3
|
-
|
6
|
+
unless String.instance_methods.include?(:humanize)
|
7
|
+
def humanize
|
8
|
+
self.split(/_|-| /).join(' ')
|
9
|
+
end
|
4
10
|
end
|
5
11
|
|
6
|
-
|
7
|
-
|
12
|
+
unless String.instance_methods.include?(:titleize)
|
13
|
+
def titleize
|
14
|
+
self.split(/_|-| /).each{|word| word[0...1] = word[0...1].upcase}.join(' ')
|
15
|
+
end
|
8
16
|
end
|
9
17
|
|
10
|
-
|
11
|
-
|
18
|
+
unless String.instance_methods.include?(:empty?)
|
19
|
+
def empty?
|
20
|
+
self.length < 1
|
21
|
+
end
|
12
22
|
end
|
13
23
|
|
14
|
-
|
15
|
-
|
24
|
+
unless String.instance_methods.include?(:pluralize)
|
25
|
+
def pluralize
|
26
|
+
Inflector.inflections.pluralize self
|
27
|
+
end
|
16
28
|
end
|
17
29
|
|
18
|
-
|
19
|
-
|
30
|
+
unless String.instance_methods.include?(:singularize)
|
31
|
+
def singularize
|
32
|
+
Inflector.inflections.singularize self
|
33
|
+
end
|
20
34
|
end
|
21
35
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
36
|
+
unless String.instance_methods.include?(:camelize)
|
37
|
+
def camelize(uppercase_first_letter = true)
|
38
|
+
string = self.dup
|
39
|
+
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) do
|
40
|
+
new_word = $2.downcase
|
41
|
+
new_word[0] = new_word[0].upcase
|
42
|
+
new_word = "/#{new_word}" if $1 == '/'
|
43
|
+
new_word
|
44
|
+
end
|
45
|
+
if uppercase_first_letter && uppercase_first_letter != :lower
|
46
|
+
string[0] = string[0].upcase
|
47
|
+
else
|
48
|
+
string[0] = string[0].downcase
|
49
|
+
end
|
50
|
+
string.gsub!('/', '::')
|
51
|
+
string
|
29
52
|
end
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
53
|
+
end
|
54
|
+
|
55
|
+
unless String.instance_methods.include?(:underscore)
|
56
|
+
def underscore
|
57
|
+
word = self.dup
|
58
|
+
word.gsub!(/::/, '/')
|
59
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
|
60
|
+
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
61
|
+
word.tr!("-", "_")
|
62
|
+
word.downcase!
|
63
|
+
word
|
34
64
|
end
|
35
|
-
string.gsub!('/', '::')
|
36
|
-
string
|
37
|
-
end
|
38
|
-
|
39
|
-
def underscore
|
40
|
-
word = self.dup
|
41
|
-
word.gsub!(/::/, '/')
|
42
|
-
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
|
43
|
-
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
44
|
-
word.tr!("-", "_")
|
45
|
-
word.downcase!
|
46
|
-
word
|
47
65
|
end
|
48
66
|
end
|
49
67
|
|
@@ -378,9 +378,15 @@ module MotionModel
|
|
378
378
|
self.send("#{field_name}=", Time.now) if self.respond_to? field_name
|
379
379
|
end
|
380
380
|
|
381
|
+
# Stub methods for hook protocols
|
382
|
+
def before_save(*); end
|
383
|
+
def after_save(*); end
|
384
|
+
def before_delete(*); end
|
385
|
+
def after_delete(*); end
|
386
|
+
|
381
387
|
def call_hook(hook_name, postfix)
|
382
388
|
hook = "#{hook_name}_#{postfix}"
|
383
|
-
self.send(hook, self)
|
389
|
+
self.send(hook, self)
|
384
390
|
end
|
385
391
|
|
386
392
|
def call_hooks(hook_name, &block)
|
@@ -442,13 +448,6 @@ module MotionModel
|
|
442
448
|
column_named(column_name).options
|
443
449
|
end
|
444
450
|
|
445
|
-
# True if this object responds to the method or
|
446
|
-
# property, otherwise false.
|
447
|
-
alias_method :old_respond_to?, :respond_to?
|
448
|
-
def respond_to?(method)
|
449
|
-
column_named(method) || old_respond_to?(method)
|
450
|
-
end
|
451
|
-
|
452
451
|
def dirty?
|
453
452
|
@dirty
|
454
453
|
end
|
data/lib/motion_model/version.rb
CHANGED
data/motion_model.gemspec
CHANGED
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.4.
|
4
|
+
version: 0.4.1
|
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-03-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bubble-wrap
|
@@ -27,6 +27,22 @@ dependencies:
|
|
27
27
|
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: 1.1.4
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: motion-support
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.1.0
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.1.0
|
30
46
|
description: Simple model and validation mixins for RubyMotion
|
31
47
|
email:
|
32
48
|
- sxross@gmail.com
|
@@ -49,7 +65,6 @@ files:
|
|
49
65
|
- lib/motion_model/model/formotion.rb
|
50
66
|
- lib/motion_model/model/model.rb
|
51
67
|
- lib/motion_model/model/model_casts.rb
|
52
|
-
- lib/motion_model/model/persistence.rb
|
53
68
|
- lib/motion_model/model/transaction.rb
|
54
69
|
- lib/motion_model/validatable.rb
|
55
70
|
- lib/motion_model/version.rb
|
@@ -64,7 +79,6 @@ files:
|
|
64
79
|
- spec/model_hook_spec.rb
|
65
80
|
- spec/model_spec.rb
|
66
81
|
- spec/notification_spec.rb
|
67
|
-
- spec/persistence_spec.rb
|
68
82
|
- spec/relation_spec.rb
|
69
83
|
- spec/transaction_spec.rb
|
70
84
|
- spec/validation_spec.rb
|
@@ -103,7 +117,6 @@ test_files:
|
|
103
117
|
- spec/model_hook_spec.rb
|
104
118
|
- spec/model_spec.rb
|
105
119
|
- spec/notification_spec.rb
|
106
|
-
- spec/persistence_spec.rb
|
107
120
|
- spec/relation_spec.rb
|
108
121
|
- spec/transaction_spec.rb
|
109
122
|
- spec/validation_spec.rb
|
@@ -1,102 +0,0 @@
|
|
1
|
-
module MotionModel
|
2
|
-
class PersistFileError < Exception; end
|
3
|
-
|
4
|
-
module Model
|
5
|
-
module PublicClassMethods
|
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 if 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
|
-
bulk_update do
|
25
|
-
collection = NSKeyedUnarchiver.unarchiveObjectWithData(data)
|
26
|
-
end
|
27
|
-
return self
|
28
|
-
end
|
29
|
-
else
|
30
|
-
return false
|
31
|
-
end
|
32
|
-
end
|
33
|
-
# Serializes data to a persistent store (file, in this
|
34
|
-
# terminology). Serialization is synchronous, so this
|
35
|
-
# will pause your run loop until complete.
|
36
|
-
#
|
37
|
-
# +file_name+ is the name of the persistent store you
|
38
|
-
# want to use. If you omit this, it will use the last
|
39
|
-
# remembered file name.
|
40
|
-
#
|
41
|
-
# Raises a +MotionModel::PersistFileError+ on failure.
|
42
|
-
def serialize_to_file(file_name = nil)
|
43
|
-
@file_name = file_name if file_name
|
44
|
-
error_ptr = Pointer.new(:object)
|
45
|
-
|
46
|
-
data = NSKeyedArchiver.archivedDataWithRootObject @collection
|
47
|
-
unless data.writeToFile(documents_file(@file_name), options: NSDataWritingAtomic, error: error_ptr)
|
48
|
-
# De-reference the pointer.
|
49
|
-
error = error_ptr[0]
|
50
|
-
|
51
|
-
# Now we can use the `error' object.
|
52
|
-
raise MotionModel::PersistFileError.new "Error when writing data: #{error}"
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
|
57
|
-
def documents_file(file_name)
|
58
|
-
file_path = File.join NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true), file_name
|
59
|
-
file_path
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def initWithCoder(coder)
|
64
|
-
self.init
|
65
|
-
|
66
|
-
new_tag_id = 1
|
67
|
-
columns.each do |attr|
|
68
|
-
next if self.class.has_relation?(attr)
|
69
|
-
# If a model revision has taken place, don't try to decode
|
70
|
-
# something that's not there.
|
71
|
-
if coder.containsValueForKey(attr.to_s)
|
72
|
-
value = coder.decodeObjectForKey(attr.to_s)
|
73
|
-
self.send("#{attr}=", value)
|
74
|
-
else
|
75
|
-
self.send("#{attr}=", nil)
|
76
|
-
end
|
77
|
-
|
78
|
-
# re-issue tags to make sure they are unique
|
79
|
-
@tag = new_tag_id
|
80
|
-
new_tag_id += 1
|
81
|
-
end
|
82
|
-
save
|
83
|
-
|
84
|
-
self
|
85
|
-
end
|
86
|
-
|
87
|
-
# Follow Apple's recommendation not to encode missing
|
88
|
-
# values.
|
89
|
-
def encodeWithCoder(coder)
|
90
|
-
columns.each do |attr|
|
91
|
-
# Serialize attributes except the proxy has_many and belongs_to ones.
|
92
|
-
unless [:belongs_to, :has_many].include? column_named(attr).type
|
93
|
-
value = self.send(attr)
|
94
|
-
unless value.nil?
|
95
|
-
coder.encodeObject(value, forKey: attr.to_s)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
end
|
102
|
-
end
|
data/spec/persistence_spec.rb
DELETED
@@ -1,187 +0,0 @@
|
|
1
|
-
class PersistTask
|
2
|
-
include MotionModel::Model
|
3
|
-
include MotionModel::ArrayModelAdapter
|
4
|
-
columns :name, :desc
|
5
|
-
end
|
6
|
-
|
7
|
-
describe 'persistence' do
|
8
|
-
before do
|
9
|
-
PersistTask.delete_all
|
10
|
-
%w(one two three).each do |task|
|
11
|
-
@tasks = PersistTask.create(:name => "name #{task}")
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
it "serializes data" do
|
16
|
-
lambda{PersistTask.serialize_to_file('test.dat')}.should.not.raise
|
17
|
-
end
|
18
|
-
|
19
|
-
it 'reads persisted model data' do
|
20
|
-
PersistTask.serialize_to_file('test.dat')
|
21
|
-
|
22
|
-
PersistTask.delete_all
|
23
|
-
|
24
|
-
PersistTask.count.should == 0
|
25
|
-
|
26
|
-
tasks = PersistTask.deserialize_from_file('test.dat')
|
27
|
-
|
28
|
-
PersistTask.count.should == 3
|
29
|
-
PersistTask.first.name.should == 'name one'
|
30
|
-
PersistTask.last.name.should == 'name three'
|
31
|
-
end
|
32
|
-
|
33
|
-
describe 'model change resiliency' do
|
34
|
-
it 'column addition' do
|
35
|
-
class Foo
|
36
|
-
include MotionModel::Model
|
37
|
-
include MotionModel::ArrayModelAdapter
|
38
|
-
columns :name => :string
|
39
|
-
end
|
40
|
-
@foo = Foo.create(:name=> 'Bob')
|
41
|
-
Foo.serialize_to_file('test.dat')
|
42
|
-
|
43
|
-
@foo.should.not.respond_to :address
|
44
|
-
|
45
|
-
class Foo
|
46
|
-
include MotionModel::Model
|
47
|
-
include MotionModel::ArrayModelAdapter
|
48
|
-
columns :name => :string,
|
49
|
-
:address => :string
|
50
|
-
end
|
51
|
-
Foo.deserialize_from_file('test.dat')
|
52
|
-
|
53
|
-
@foo = Foo.first
|
54
|
-
|
55
|
-
@foo.name.should == 'Bob'
|
56
|
-
@foo.address.should == nil
|
57
|
-
@foo.should.respond_to :address
|
58
|
-
Foo.length.should == 1
|
59
|
-
end
|
60
|
-
|
61
|
-
it "column removal" do
|
62
|
-
class Foo
|
63
|
-
include MotionModel::Model
|
64
|
-
include MotionModel::ArrayModelAdapter
|
65
|
-
columns :name => :string, :desc => :string
|
66
|
-
end
|
67
|
-
|
68
|
-
@foo = Foo.create(:name=> 'Bob', :desc => 'who cares anyway?')
|
69
|
-
Foo.serialize_to_file('test.dat')
|
70
|
-
|
71
|
-
@foo.should.respond_to :desc
|
72
|
-
|
73
|
-
Object.send(:remove_const, :Foo)
|
74
|
-
class Foo
|
75
|
-
include MotionModel::Model
|
76
|
-
include MotionModel::ArrayModelAdapter
|
77
|
-
columns :name => :string,
|
78
|
-
:address => :string
|
79
|
-
end
|
80
|
-
Foo.deserialize_from_file('test.dat')
|
81
|
-
|
82
|
-
# @foo = Foo.first
|
83
|
-
# @foo.name.should == 'Bob'
|
84
|
-
# @foo.address.should == nil
|
85
|
-
# @foo.should.not.respond_to :desc
|
86
|
-
# @foo.should.respond_to :address
|
87
|
-
# Foo.length.should == 1
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
describe "remembering filename" do
|
92
|
-
class Foo
|
93
|
-
include MotionModel::Model
|
94
|
-
include MotionModel::ArrayModelAdapter
|
95
|
-
columns :name => :string
|
96
|
-
end
|
97
|
-
|
98
|
-
before do
|
99
|
-
Foo.delete_all
|
100
|
-
@foo = Foo.create(:name => 'Bob')
|
101
|
-
end
|
102
|
-
|
103
|
-
it "deserializes from last file if no filename given (previous method serialize)" do
|
104
|
-
Foo.serialize_to_file('test.dat')
|
105
|
-
Foo.delete_all
|
106
|
-
Foo.count.should == 0
|
107
|
-
Foo.deserialize_from_file
|
108
|
-
Foo.count.should == 1
|
109
|
-
end
|
110
|
-
|
111
|
-
it "deserializes from last file if no filename given (previous method deserialize)" do
|
112
|
-
Foo.serialize_to_file('test.dat')
|
113
|
-
Foo.serialize_to_file('bogus.dat') # serialize sets default filename to something bogus
|
114
|
-
File.delete Foo.documents_file('bogus.dat') # and we get rid of that file
|
115
|
-
Foo.deserialize_from_file('test.dat') # so we'll be sure the default filename last was set by deserialize
|
116
|
-
Foo.delete_all
|
117
|
-
Foo.count.should == 0
|
118
|
-
Foo.deserialize_from_file
|
119
|
-
Foo.count.should == 1
|
120
|
-
end
|
121
|
-
|
122
|
-
it "serializes to last file if no filename given (previous method serialize)" do
|
123
|
-
Foo.serialize_to_file('test.dat')
|
124
|
-
Foo.create(:name => 'Ted')
|
125
|
-
Foo.serialize_to_file
|
126
|
-
Foo.delete_all
|
127
|
-
Foo.count.should == 0
|
128
|
-
Foo.deserialize_from_file('test.dat')
|
129
|
-
Foo.count.should == 2
|
130
|
-
end
|
131
|
-
|
132
|
-
it "serializes to last file if no filename given (previous method deserialize)" do
|
133
|
-
Foo.serialize_to_file('test.dat')
|
134
|
-
Foo.delete_all
|
135
|
-
Foo.serialize_to_file('bogus.dat') # serialize sets default filename to something bogus
|
136
|
-
File.delete Foo.documents_file('bogus.dat') # and we get rid of that file
|
137
|
-
Foo.deserialize_from_file('test.dat') # so we'll be sure the default filename was last set by deserialize
|
138
|
-
Foo.create(:name => 'Ted')
|
139
|
-
Foo.serialize_to_file
|
140
|
-
Foo.delete_all
|
141
|
-
Foo.count.should == 0
|
142
|
-
Foo.deserialize_from_file('test.dat')
|
143
|
-
Foo.count.should == 2
|
144
|
-
end
|
145
|
-
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
class Parent
|
150
|
-
include MotionModel::Model
|
151
|
-
include MotionModel::ArrayModelAdapter
|
152
|
-
columns :name
|
153
|
-
has_many :children
|
154
|
-
end
|
155
|
-
|
156
|
-
class Child
|
157
|
-
include MotionModel::Model
|
158
|
-
include MotionModel::ArrayModelAdapter
|
159
|
-
columns :name
|
160
|
-
belongs_to :parent
|
161
|
-
end
|
162
|
-
|
163
|
-
describe "serialization of relations" do
|
164
|
-
before do
|
165
|
-
parent = Parent.create(:name => 'BoB')
|
166
|
-
parent.children.create :name => 'Fergie'
|
167
|
-
parent.children.create :name => 'Will I Am'
|
168
|
-
end
|
169
|
-
|
170
|
-
it "is wired up right" do
|
171
|
-
Parent.first.name.should == 'BoB'
|
172
|
-
Parent.first.children.count.should == 2
|
173
|
-
end
|
174
|
-
|
175
|
-
it "serializes and deserializes properly" do
|
176
|
-
Parent.serialize_to_file('parents.dat')
|
177
|
-
Child.serialize_to_file('children.dat')
|
178
|
-
Parent.delete_all
|
179
|
-
Child.delete_all
|
180
|
-
Parent.deserialize_from_file('parents.dat')
|
181
|
-
Child.deserialize_from_file('children.dat')
|
182
|
-
Parent.first.name.should == 'BoB'
|
183
|
-
Parent.first.children.count.should == 2
|
184
|
-
Parent.first.children.first.name.should == 'Fergie'
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|