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 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
@@ -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
- def humanize
3
- self.split(/_|-| /).join(' ')
6
+ unless String.instance_methods.include?(:humanize)
7
+ def humanize
8
+ self.split(/_|-| /).join(' ')
9
+ end
4
10
  end
5
11
 
6
- def titleize
7
- self.split(/_|-| /).each{|word| word[0...1] = word[0...1].upcase}.join(' ')
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
- def empty?
11
- self.length < 1
18
+ unless String.instance_methods.include?(:empty?)
19
+ def empty?
20
+ self.length < 1
21
+ end
12
22
  end
13
23
 
14
- def pluralize
15
- Inflector.inflections.pluralize self
24
+ unless String.instance_methods.include?(:pluralize)
25
+ def pluralize
26
+ Inflector.inflections.pluralize self
27
+ end
16
28
  end
17
29
 
18
- def singularize
19
- Inflector.inflections.singularize self
30
+ unless String.instance_methods.include?(:singularize)
31
+ def singularize
32
+ Inflector.inflections.singularize self
33
+ end
20
34
  end
21
35
 
22
- def camelize(uppercase_first_letter = true)
23
- string = self.dup
24
- string.gsub!(/(?:_|(\/))([a-z\d]*)/i) do
25
- new_word = $2.downcase
26
- new_word[0] = new_word[0].upcase
27
- new_word = "/#{new_word}" if $1 == '/'
28
- new_word
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
- if uppercase_first_letter && uppercase_first_letter != :lower
31
- string[0] = string[0].upcase
32
- else
33
- string[0] = string[0].downcase
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) if respond_to? hook.to_sym
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
@@ -4,5 +4,5 @@
4
4
  # or forward port their code to take advantage
5
5
  # of adapters.
6
6
  module MotionModel
7
- VERSION = "0.4.0"
7
+ VERSION = "0.4.1"
8
8
  end
data/motion_model.gemspec CHANGED
@@ -13,5 +13,6 @@ Gem::Specification.new do |gem|
13
13
  gem.name = "motion_model"
14
14
  gem.require_paths = ["lib"]
15
15
  gem.add_dependency 'bubble-wrap', '~> 1.1.4'
16
+ gem.add_dependency 'motion-support', '>=0.1.0'
16
17
  gem.version = MotionModel::VERSION
17
18
  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.4.0
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-02-19 00:00:00.000000000 Z
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
@@ -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
-