motion_model 0.4.0 → 0.4.1
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/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
|
-
|