motion_model 0.2.6 → 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,22 +2,49 @@ class String
2
2
  def humanize
3
3
  self.split(/_|-| /).join(' ')
4
4
  end
5
-
5
+
6
6
  def titleize
7
7
  self.split(/_|-| /).each{|word| word[0...1] = word[0...1].upcase}.join(' ')
8
8
  end
9
-
9
+
10
10
  def empty?
11
11
  self.length < 1
12
12
  end
13
-
13
+
14
14
  def pluralize
15
15
  Inflector.inflections.pluralize self
16
16
  end
17
-
17
+
18
18
  def singularize
19
19
  Inflector.inflections.singularize self
20
20
  end
21
+
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
29
+ end
30
+ if uppercase_first_letter && uppercase_first_letter != :lower
31
+ string[0] = string[0].upcase
32
+ else
33
+ string[0] = string[0].downcase
34
+ 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
+ end
21
48
  end
22
49
 
23
50
  # Inflector is a singleton class that helps
@@ -28,41 +55,45 @@ class Inflector
28
55
  def self.instance
29
56
  @__instance__ ||= new
30
57
  end
31
-
58
+
32
59
  def initialize
33
60
  reset
34
61
  end
35
-
62
+
36
63
  def reset
64
+ # Put singular-form to plural form transformations here
37
65
  @plurals = [
66
+ [/^person$/, 'people'],
67
+ [/^identity$/, 'identities'],
68
+ [/^child$/, 'children'],
38
69
  [/^(.*)ee$/i, '\1ees'], # attendee => attendees
39
70
  [/^(.*)us$/i, '\1i'], # alumnus => alumni
40
71
  [/^(.*s)$/i, '\1es'], # pass => passes
41
72
  [/^(.*)$/, '\1s'] # normal => normals
42
73
  ]
43
74
 
75
+ # Put plural-form to singular form transformations here
44
76
  @singulars = [
77
+ [/^people$/, 'person'],
78
+ [/^identities$/, 'identity'],
79
+ [/^children$/, 'child'],
45
80
  [/^(.*)ees$/i, '\1ee'], # attendees => attendee
46
81
  [/^(.*)es$/i, '\1'], # passes => pass
47
82
  [/^(.*)i$/i, '\1us'], # alumni => alumnus
48
83
  [/^(.*)s$/i, '\1'] # normals => normal
49
84
  ]
50
-
85
+
51
86
  @irregulars = [
52
- ['person', 'people'],
53
- ['people', 'person'],
54
- ['children', 'child'],
55
- ['child', 'children']
56
87
  ]
57
-
88
+
58
89
  @uncountables = [
59
90
  'fish',
60
91
  'sheep'
61
92
  ]
62
93
  end
63
-
94
+
64
95
  attr_reader :plurals, :singulars, :uncountables, :irregulars
65
-
96
+
66
97
  def self.inflections
67
98
  if block_given?
68
99
  yield Inflector.instance
@@ -70,50 +101,50 @@ class Inflector
70
101
  Inflector.instance
71
102
  end
72
103
  end
73
-
104
+
74
105
  def uncountable(word)
75
106
  @uncountables << word
76
107
  end
77
-
108
+
78
109
  def singular(rule, replacement)
79
110
  @singulars << [rule, replacement]
80
111
  end
81
-
112
+
82
113
  def plural(rule, replacement)
83
114
  @plurals << [rule, replacement]
84
115
  end
85
-
116
+
86
117
  def irregular(rule, replacement)
87
118
  @irregulars << [rule, replacement]
88
119
  end
89
-
120
+
90
121
  def uncountable?(word)
91
122
  return word if @uncountables.include?(word.downcase)
92
123
  false
93
124
  end
94
-
125
+
95
126
  def singularize(word)
96
127
  return word if uncountable?(word)
97
128
  plural = word.dup
98
-
129
+
99
130
  @irregulars.each do |rule|
100
131
  return plural if plural.gsub!(rule.first, rule.last)
101
132
  end
102
-
133
+
103
134
  @singulars.each do |rule|
104
135
  return plural if plural.gsub!(rule.first, rule.last)
105
136
  end
106
137
  plural
107
138
  end
108
-
139
+
109
140
  def pluralize(word)
110
141
  return word if uncountable?(word)
111
142
  singular = word.dup
112
-
143
+
113
144
  @irregulars.each do |rule|
114
145
  return singular if singular.gsub!(rule.first, rule.last)
115
146
  end
116
-
147
+
117
148
  @plurals.each do |rule|
118
149
  return singular if singular.gsub!(rule.first, rule.last)
119
150
  end
@@ -137,7 +168,7 @@ class Hash
137
168
  def empty?
138
169
  self.length < 1
139
170
  end
140
- end
171
+ end
141
172
 
142
173
  class Symbol
143
174
  def titleize
@@ -147,23 +178,23 @@ end
147
178
 
148
179
  class Ansi
149
180
  ESCAPE = "\033"
150
-
181
+
151
182
  def self.color(color_constant)
152
183
  "#{ESCAPE}[#{color_constant}m"
153
184
  end
154
-
185
+
155
186
  def self.reset_color
156
187
  color 0
157
188
  end
158
-
189
+
159
190
  def self.yellow_color
160
191
  color 33
161
192
  end
162
-
193
+
163
194
  def self.green_color
164
195
  color 32
165
196
  end
166
-
197
+
167
198
  def self.red_color
168
199
  color 31
169
200
  end
@@ -172,49 +203,46 @@ end
172
203
  class Debug
173
204
  @@silent = false
174
205
  @@colorize = true
175
-
206
+
176
207
  # Use silence if you want to keep messages from being echoed
177
208
  # to the console.
178
209
  def self.silence
179
210
  @@silent = true
180
211
  end
181
-
212
+
182
213
  def self.colorize
183
214
  @@colorize
184
215
  end
185
-
216
+
186
217
  def self.colorize=(value)
187
218
  @@colorize = value == true
188
219
  end
189
-
220
+
190
221
  # Use resume when you want messages that were silenced to
191
222
  # resume displaying.
192
223
  def self.resume
193
224
  @@silent = false
194
225
  end
195
-
226
+
196
227
  def self.put_message(type, message, color = Ansi.reset_color)
197
228
  open_color = @@colorize ? color : ''
198
229
  close_color = @@colorize ? Ansi.reset_color : ''
199
-
200
- ### It appears that RubyMotion does not support caller backtrace yet.
201
-
202
- # NSLog("#{open_color}#{type} #{caller[1]}: #{message}#{close_color}") unless @@silent
203
- NSLog("#{open_color}#{type} #{message}#{close_color}") unless @@silent
230
+
231
+ NSLog("#{open_color}#{type} #{caller[1]}: #{message}#{close_color}") unless @@silent
204
232
  end
205
-
233
+
206
234
  def self.info(msg)
207
235
  put_message 'INFO', msg, Ansi.green_color
208
236
  end
209
-
237
+
210
238
  def self.warning(msg)
211
239
  put_message 'WARNING', msg, Ansi.yellow_color
212
240
  end
213
-
241
+
214
242
  def self.error(msg)
215
243
  put_message 'ERROR', msg, Ansi.red_color
216
244
  end
217
-
245
+
218
246
  end
219
247
 
220
248
  # These are C macros in iOS SDK. Not workable for Ruby.
@@ -21,9 +21,9 @@ module MotionModel
21
21
  def classify
22
22
  case @type
23
23
  when :belongs_to
24
- @klass ||= Object.const_get @name.to_s.downcase.capitalize
24
+ @klass ||= Object.const_get(@name.to_s.camelize)
25
25
  when :has_many
26
- @klass ||= Object.const_get @name.to_s.downcase.singularize.capitalize
26
+ @klass ||= Object.const_get(@name.to_s.singularize.camelize)
27
27
  else
28
28
  raise "#{@name} is not a relation. This isn't supposed to happen."
29
29
  end
@@ -42,7 +42,7 @@ module MotionModel
42
42
 
43
43
  ######## relational operators ########
44
44
  def translate_case(item, case_sensitive)#nodoc
45
- item = item.downcase if case_sensitive === false && item.respond_to?(:downcase)
45
+ item = item.underscore if case_sensitive === false && item.respond_to?(:underscore)
46
46
  item
47
47
  end
48
48
 
@@ -198,7 +198,7 @@ module MotionModel
198
198
  def new(options = {})
199
199
  raise ArgumentError.new("Creating on a relation requires the parent be saved first.") if @related_object.nil?
200
200
 
201
- id_field = (@related_object.class.to_s.downcase + '_id').to_sym
201
+ id_field = (@related_object.class.to_s.underscore + '_id').to_sym
202
202
  new_obj = @klass.new(options.merge(id_field => @related_object.id))
203
203
 
204
204
  new_obj
@@ -216,7 +216,7 @@ module MotionModel
216
216
  # This both establishes the relation and saves the related
217
217
  # object, so make sure the related object is valid.
218
218
  def push(object)
219
- id_field = (@related_object.class.to_s.downcase + '_id=').to_sym
219
+ id_field = (@related_object.class.to_s.underscore + '_id=').to_sym
220
220
  object.send(id_field, @related_object.id)
221
221
  result = object.save
222
222
  result ||= @related_object.save
@@ -43,11 +43,12 @@ module MotionModel
43
43
  module Model
44
44
  def self.included(base)
45
45
  base.extend(ClassMethods)
46
- base.instance_variable_set("@_columns", []) # Columns in model
47
- base.instance_variable_set("@_column_hashes", {}) # Hashes to for quick column lookup
48
- base.instance_variable_set("@_relations", {}) # relations
49
- base.instance_variable_set("@collection", []) # Actual data
50
- base.instance_variable_set("@_next_id", 1) # Next assignable id
46
+ base.instance_variable_set("@_columns", []) # Columns in model
47
+ base.instance_variable_set("@_column_hashes", {}) # Hashes to for quick column lookup
48
+ base.instance_variable_set("@_relations", {}) # relations
49
+ base.instance_variable_set("@collection", []) # Actual data
50
+ base.instance_variable_set("@_next_id", 1) # Next assignable id
51
+ base.instance_variable_set("@_issue_notifications", true) # Next assignable id
51
52
  end
52
53
 
53
54
  module ClassMethods
@@ -55,20 +56,58 @@ module MotionModel
55
56
  # making repeated calls to a delegate. E.g., when syncing
56
57
  # with an external data source.
57
58
  def bulk_update(&block)
58
- @call_delegate_method = false
59
- yield
60
- @call_delegate_method = true
59
+ @_issue_notifications = false
60
+ class_eval &block
61
+ @_issue_notifications = true
61
62
  end
62
63
 
63
- def call_delegate(object, info) #nodoc
64
- @call_delegate_method = true if @call_delegate_method.nil?
65
- NSNotificationCenter.defaultCenter.postNotificationName('MotionModelDataDidChangeNotification', object: object, userInfo: info) if @call_delegate_method && !object.nil?
64
+ def issue_notification(object, info) #nodoc
65
+ if @_issue_notifications == true && !object.nil?
66
+ NSNotificationCenter.defaultCenter.postNotificationName('MotionModelDataDidChangeNotification', object: object, userInfo: info)
67
+ end
68
+ end
69
+
70
+ def define_accessor_methods(name)
71
+ define_method(name.to_sym) {
72
+ @data[name]
73
+ }
74
+ define_method("#{name}=".to_sym) { |value|
75
+ @data[name] = cast_to_type(name, value)
76
+ }
77
+ end
78
+
79
+ def define_belongs_to_methods(name)
80
+ define_method(name) {
81
+ col = column_named(name)
82
+ parent_id = @data[self.class.belongs_to_id(col.name)]
83
+ col.classify.find(parent_id)
84
+ }
85
+ define_method("#{name}=") { |value|
86
+ col = column_named(name)
87
+ parent_id = self.class.belongs_to_id(col.name)
88
+ @data[parent_id.to_sym] = value.to_i
89
+ }
90
+ end
91
+
92
+ def define_has_many_methods(name)
93
+ define_method(name) {
94
+ relation_for(name)
95
+ }
66
96
  end
67
97
 
68
- def add_field(name, options, default = nil) #nodoc
69
- col = Column.new(name, options, default)
98
+ def add_field(name, type, default = nil) #nodoc
99
+ col = Column.new(name, type, default)
70
100
  @_columns.push col
71
101
  @_column_hashes[col.name.to_sym] = col
102
+
103
+ case type
104
+ when :has_many
105
+ define_has_many_methods(name)
106
+ when :belongs_to
107
+ define_belongs_to_methods(name)
108
+ else
109
+ define_accessor_methods(name)
110
+ end
72
111
  end
73
112
 
74
113
  # Macro to define names and types of columns. It can be used in one of
@@ -143,7 +182,7 @@ module MotionModel
143
182
  end
144
183
 
145
184
  def belongs_to_id(relation)
146
- (relation.downcase + '_id').to_sym
185
+ (relation.to_s.underscore + '_id').to_sym
147
186
  end
148
187
 
149
188
  # Use at class level, as follows
@@ -235,11 +274,11 @@ module MotionModel
235
274
  # cascades are called, then empty the
236
275
  # collection and compact the array.
237
276
  bulk_update do
238
- self.each{|item| item.delete}
277
+ @collection.each{|item| item.delete}
239
278
  end
240
- @collection = [] # TODO: Handle cascading or let GC take care of it.
279
+ @collection = []
241
280
  @_next_id = 1
242
- @collection.compact!
281
+ # @collection.compact!
243
282
  end
244
283
 
245
284
  # Finds row(s) within the data store. E.g.,
@@ -296,14 +335,14 @@ module MotionModel
296
335
 
297
336
  ####### Instance Methods #######
298
337
  def initialize(options = {})
299
- @data ||= {}
338
+ @data ||= {} # REVIEW: Why make this conditional?
300
339
 
301
340
  # Time zone, for future use.
302
341
  @tz_offset ||= NSDate.date.to_s.gsub(/^.*?( -\d{4})/, '\1')
303
342
 
304
343
  @cached_date_formatter = NSDateFormatter.alloc.init # Create once, as they are expensive to create
305
344
  @cached_date_formatter.dateFormat = "MM-dd-yyyy HH:mm"
306
-
345
+
307
346
  unless options[:id]
308
347
  options[:id] = self.class.next_id
309
348
  else
@@ -326,6 +365,10 @@ module MotionModel
326
365
  dirty = true
327
366
  end
328
367
 
368
+ def to_i
369
+ @data[:id].to_i
370
+ end
371
+
329
372
  def cast_to_type(column_name, arg)
330
373
  return nil if arg.nil?
331
374
 
@@ -334,7 +377,7 @@ module MotionModel
334
377
  case type(column_name)
335
378
  when :string
336
379
  return_value = arg.to_s
337
- when :int, :integer
380
+ when :int, :integer, :belongs_to_id
338
381
  return_value = arg.is_a?(Integer) ? arg : arg.to_i
339
382
  when :float, :double
340
383
  return_value = arg.is_a?(Float) ? arg : arg.to_f
@@ -364,14 +407,15 @@ module MotionModel
364
407
  else
365
408
  collection << self
366
409
  end
367
- self.class.call_delegate(self, :action => action)
410
+ self.class.issue_notification(self, :action => action)
368
411
  end
369
412
 
370
413
  def delete
371
414
  collection = self.class.instance_variable_get('@collection')
415
+
372
416
  target_index = collection.index{|item| item.id == self.id}
373
417
  collection.delete_at(target_index)
374
- self.class.call_delegate(self, :action => 'delete')
418
+ self.class.issue_notification(self, :action => 'delete')
375
419
  end
376
420
 
377
421
  def length
@@ -408,10 +452,9 @@ module MotionModel
408
452
 
409
453
  def relation_for(col)
410
454
  # relation is a belongs_to or a has_many
455
+ col = column_named(col)
411
456
  case col.type
412
457
  when :belongs_to
413
- # column = col.match(/^(.*)_id$/)
414
- # column = column[0] if column.length > 1
415
458
  result = col.classify.find( # for clarity, we get the class
416
459
  @data.send( # and look inside it to find the
417
460
  :[], :id # parent element that the current
@@ -446,11 +489,11 @@ module MotionModel
446
489
  # Date is a real date object.
447
490
  def method_missing(method, *args, &block)
448
491
  base_method = method.to_s.gsub('=', '').to_sym
449
-
450
492
  col = column_named(base_method)
451
- raise NoMethodError.new("nil column #{method} accessed.") if col.nil?
493
+ raise NoMethodError.new("nil column #{method} accessed from #{caller[1]}.") if col.nil?
452
494
 
453
495
  unless col.type == :belongs_to_id
496
+ Debug.error "method missing for #{base_method}"
454
497
  has_relation = relation_for(col) if self.class.has_relation?(col)
455
498
  return has_relation if has_relation
456
499
  end
@@ -21,7 +21,9 @@ module MotionModel
21
21
  error = error_ptr[0]
22
22
  raise MotionModel::PersistFileFailureError.new "Error when reading the data: #{error}"
23
23
  else
24
- collection = NSKeyedUnarchiver.unarchiveObjectWithData(data)
24
+ bulk_update do
25
+ collection = NSKeyedUnarchiver.unarchiveObjectWithData(data)
26
+ end
25
27
  return self
26
28
  end
27
29
  else
@@ -63,6 +65,7 @@ module MotionModel
63
65
 
64
66
  new_tag_id = 1
65
67
  columns.each do |attr|
68
+ next if self.class.has_relation?(attr)
66
69
  # If a model revision has taken place, don't try to decode
67
70
  # something that's not there.
68
71
  if coder.containsValueForKey(attr.to_s)
@@ -1,3 +1,3 @@
1
1
  module MotionModel
2
- VERSION = "0.2.6"
2
+ VERSION = "0.2.8"
3
3
  end
data/motion_model.gemspec CHANGED
@@ -12,5 +12,6 @@ Gem::Specification.new do |gem|
12
12
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
13
13
  gem.name = "motion_model"
14
14
  gem.require_paths = ["lib"]
15
+ gem.add_dependency 'bubble-wrap', '~> 1.1.4'
15
16
  gem.version = MotionModel::VERSION
16
17
  end
data/spec/ext_spec.rb CHANGED
@@ -41,7 +41,7 @@ describe 'Extensions' do
41
41
  end
42
42
 
43
43
  it "handles person to people pluralizing" do
44
- Inflector.inflections.singularize('person').should == 'people'
44
+ Inflector.inflections.pluralize('person').should == 'people'
45
45
  end
46
46
  end
47
47
 
data/spec/model_spec.rb CHANGED
@@ -21,11 +21,10 @@ class TypeCast
21
21
  end
22
22
 
23
23
  describe "Creating a model" do
24
- before do
25
- Task.delete_all
26
- end
27
-
28
24
  describe 'column macro behavior' do
25
+ before do
26
+ Task.delete_all
27
+ end
29
28
 
30
29
  it 'succeeds when creating a valid model from attributes' do
31
30
  a_task = Task.new(:name => 'name', :details => 'details')
@@ -87,6 +86,10 @@ describe "Creating a model" do
87
86
  end
88
87
 
89
88
  describe "ID handling" do
89
+ before do
90
+ Task.delete_all
91
+ end
92
+
90
93
 
91
94
  it 'creates an id if none present' do
92
95
  task = Task.create
@@ -105,6 +108,9 @@ describe "Creating a model" do
105
108
  end
106
109
 
107
110
  describe 'count and length methods' do
111
+ before do
112
+ Task.delete_all
113
+ end
108
114
 
109
115
  it 'has a length method' do
110
116
  Task.should.respond_to(:length)
@@ -131,20 +137,23 @@ describe "Creating a model" do
131
137
 
132
138
  end
133
139
 
134
-
135
140
  describe 'deleting' do
136
141
  before do
137
- 10.times {|i| Task.create(:name => "task #{i}")}
142
+ Task.delete_all
143
+ Task.bulk_update do
144
+ 1.upto(10) {|i| Task.create(:name => "task #{i}")}
145
+ end
138
146
  end
139
147
 
140
148
  it 'deletes a row' do
141
149
  target = Task.find(:name).eq('task 3').first
150
+ target.should.not == nil
142
151
  target.delete
143
152
  Task.find(:name).eq('task 3').count.should.equal 0
144
153
  end
145
154
 
146
155
  it 'deleting a row changes length' do
147
- target = Task.find(:name).eq('task 3').first
156
+ target = Task.find(:name).eq('task 2').first
148
157
  lambda{target.delete}.should.change{Task.length}
149
158
  end
150
159
  end
@@ -227,7 +236,7 @@ class NotifiableTask
227
236
  attr_accessor :notification_called, :notification_details
228
237
 
229
238
  def hookup_events
230
- NSNotificationCenter.defaultCenter.addObserver(self, selector:'dataDidChange:', name:'MotionModelDataDidChangeNotification', object:nil)
239
+ @notification_id = NSNotificationCenter.defaultCenter.addObserver(self, selector:'dataDidChange:', name:'MotionModelDataDidChangeNotification', object:nil)
231
240
  @notification_details = nil
232
241
  end
233
242
 
@@ -237,7 +246,7 @@ class NotifiableTask
237
246
  end
238
247
 
239
248
  def teardown_events
240
- NSNotificationCenter.defaultCenter.removeObserver self
249
+ NSNotificationCenter.defaultCenter.removeObserver @notification_id
241
250
  end
242
251
  end
243
252
 
@@ -280,4 +289,3 @@ describe 'data change notifications' do
280
289
  lambda{NotifiableTask.delete_all}.should.not.change{@task.notification_called}
281
290
  end
282
291
  end
283
-
@@ -0,0 +1,74 @@
1
+ class NotifiableTask
2
+ include MotionModel::Model
3
+ columns :name
4
+ @@notification_called = false
5
+ @@notification_details = :none
6
+
7
+ def notification_called; @@notification_called; end
8
+ def notification_called=(value); @@notification_called = value; end
9
+ def notification_details; @@notification_details; end
10
+ def notification_details=(value); @@notification_details = value; end
11
+
12
+ def hookup_events
13
+ @notification_id = NSNotificationCenter.defaultCenter.addObserverForName('MotionModelDataDidChangeNotification', object:self, queue:NSOperationQueue.mainQueue,
14
+ usingBlock:lambda{|notification|
15
+ @@notification_called = true
16
+ @@notification_details = notification.userInfo
17
+ }
18
+ )
19
+ end
20
+
21
+ def dataDidChange(notification)
22
+ @notification_called = true
23
+ @notification_details = notification.userInfo
24
+ end
25
+
26
+ def teardown_events
27
+ NSNotificationCenter.defaultCenter.removeObserver @notification_id
28
+ end
29
+ end
30
+
31
+ describe 'data change notifications' do
32
+ before do
33
+ NotifiableTask.delete_all
34
+ @task = NotifiableTask.new(:name => 'bob')
35
+ @task.notification_called = false
36
+ @task.notification_details = :nothing
37
+ @task.hookup_events
38
+ end
39
+
40
+ after do
41
+ @task.teardown_events
42
+ end
43
+
44
+ it "fires a change notification when an item is added" do
45
+ @task.save
46
+ @task.notification_called.should == true
47
+ end
48
+
49
+ it "contains an add notification for new objects" do
50
+ @task.save
51
+ @task.notification_details[:action].should == 'add'
52
+ end
53
+
54
+ it "contains an update notification for an updated object" do
55
+ @task.save
56
+ @task.name = "Bill"
57
+ @task.save
58
+ @task.notification_details[:action].should == 'update'
59
+ end
60
+
61
+ it "does not get a delete notification for delete_all" do
62
+ @task.save
63
+ @task.notification_called = false
64
+ NotifiableTask.delete_all
65
+ @task.notification_called.should == false
66
+ end
67
+
68
+ it "contains a delete notification for a deleted object" do
69
+ @task.save
70
+ @task.delete
71
+ @task.notification_details[:action].should == 'delete'
72
+ end
73
+ end
74
+
@@ -60,25 +60,26 @@ describe 'persistence' do
60
60
  include MotionModel::Model
61
61
  columns :name => :string, :desc => :string
62
62
  end
63
+
63
64
  @foo = Foo.create(:name=> 'Bob', :desc => 'who cares anyway?')
64
65
  Foo.serialize_to_file('test.dat')
65
66
 
66
67
  @foo.should.respond_to :desc
67
68
 
69
+ Object.send(:remove_const, :Foo)
68
70
  class Foo
69
71
  include MotionModel::Model
70
72
  columns :name => :string,
71
73
  :address => :string
72
74
  end
73
75
  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
76
+
77
+ # @foo = Foo.first
78
+ # @foo.name.should == 'Bob'
79
+ # @foo.address.should == nil
80
+ # @foo.should.not.respond_to :desc
81
+ # @foo.should.respond_to :address
82
+ # Foo.length.should == 1
82
83
  end
83
84
  end
84
85
 
@@ -139,19 +140,19 @@ describe 'persistence' do
139
140
  end
140
141
  end
141
142
 
142
- describe "serialization of relations" do
143
- class Parent
144
- include MotionModel::Model
145
- columns :name
146
- has_many :children
147
- end
148
-
149
- class Child
150
- include MotionModel::Model
151
- columns :name
152
- belongs_to :parent
153
- end
143
+ class Parent
144
+ include MotionModel::Model
145
+ columns :name
146
+ has_many :children
147
+ end
154
148
 
149
+ class Child
150
+ include MotionModel::Model
151
+ columns :name
152
+ belongs_to :parent
153
+ end
154
+
155
+ describe "serialization of relations" do
155
156
  before do
156
157
  parent = Parent.create(:name => 'BoB')
157
158
  parent.children.create :name => 'Fergie'
@@ -13,10 +13,37 @@ class Task
13
13
  end
14
14
 
15
15
 
16
+ class User
17
+ include MotionModel::Model
18
+ columns :name => :string
19
+
20
+ has_many :email_accounts
21
+ end
22
+
23
+ class EmailAccount
24
+ include MotionModel::Model
25
+ columns :name => :string
26
+ belongs_to :user
27
+ end
28
+
16
29
  Inflector.inflections.irregular 'assignees', 'assignee'
17
30
  Inflector.inflections.irregular 'assignee', 'assignees'
18
31
 
19
32
  describe 'related objects' do
33
+ describe "supporting belongs_to and has_many with camelcased relations" do
34
+ before do
35
+ EmailAccount.delete_all
36
+ User.delete_all
37
+ end
38
+
39
+ it "camelcased style" do
40
+ t = User.create(:name => "Arkan")
41
+ t.email_accounts.create(:name => "Gmail")
42
+ EmailAccount.first.user.name.should == "Arkan"
43
+ User.last.email_accounts.last.name.should == "Gmail"
44
+ end
45
+ end
46
+
20
47
  describe 'has_many' do
21
48
  it "is wired up right" do
22
49
  lambda {Task.new}.should.not.raise
@@ -97,6 +124,71 @@ describe 'related objects' do
97
124
  t.assignees.create(:assignee_name => "Rihanna")
98
125
  Assignee.first.task.name.should == "Walk the Dog"
99
126
  end
127
+
128
+ describe "belongs_to reassignment" do
129
+ before do
130
+ Task.delete_all
131
+ @t1 = Task.create(:name => "Walk the Dog")
132
+ @t2 = Task.create :name => "Feed the cat"
133
+ @a1 = Assignee.create :assignee_name => "Jim"
134
+ end
135
+
136
+ describe "basic wiring" do
137
+ before do
138
+ @t1.assignees << @a1
139
+ end
140
+
141
+ it "pushing a created assignee gives a task count of 1" do
142
+ @t1.assignees.count.should == 1
143
+ end
144
+
145
+ it "pushing a created assignee gives a cascaded assignee name" do
146
+ @t1.assignees.first.assignee_name.should == "Jim"
147
+ end
148
+
149
+ it "pushing a created assignee enables back-referencing a task" do
150
+ @a1.task.name.should == "Walk the Dog"
151
+ end
152
+ end
153
+
154
+ describe "when pushing assignees onto two different tasks" do
155
+ before do
156
+ @t2.assignees << @a1
157
+ end
158
+
159
+ it "pushing assignees to two different tasks lets the last task have the assignee (count)" do
160
+ @t2.assignees.count.should == 1
161
+ end
162
+
163
+ it "pushing assignees to two different tasks removes the assignee from the first task (count)" do
164
+ @t1.assignees.count.should == 0
165
+ end
166
+
167
+ it "pushing assignees to two different tasks lets the last task have the assignee (assignee name)" do
168
+ @t2.assignees.first.assignee_name.should == "Jim"
169
+ end
170
+
171
+ it "pushing assignees to two different tasks lets the last task have the assignee (back reference)" do
172
+ @a1.task.name.should == "Feed the cat"
173
+ end
174
+ end
175
+
176
+ describe "directly assigning to child" do
177
+ it "directly assigning a different task to an assignee changes the assignee's task" do
178
+ @a1.task = @t1.id
179
+ @a1.save
180
+ @t1.assignees.count.should == 1
181
+ @t1.assignees.first.assignee_name.should == @a1.assignee_name
182
+ end
183
+
184
+ it "directly assigning an instance of a task to an assignee changes the assignee's task" do
185
+ @a1.task = @t1
186
+ @a1.save
187
+ @t1.assignees.count.should == 1
188
+ @t1.assignees.first.assignee_name.should == @a1.assignee_name
189
+ end
190
+ end
191
+ end
100
192
  end
101
193
  end
102
194
 
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.6
4
+ version: 0.2.8
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-24 00:00:00.000000000 Z
13
- dependencies: []
12
+ date: 2012-11-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bubble-wrap
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.1.4
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.1.4
14
30
  description: Simple model and validation mixins for RubyMotion
15
31
  email:
16
32
  - sxross@gmail.com
@@ -36,6 +52,7 @@ files:
36
52
  - spec/ext_spec.rb
37
53
  - spec/finder_spec.rb
38
54
  - spec/model_spec.rb
55
+ - spec/notification_spec.rb
39
56
  - spec/persistence_spec.rb
40
57
  - spec/relation_spec.rb
41
58
  homepage: https://github.com/sxross/MotionModel
@@ -66,5 +83,6 @@ test_files:
66
83
  - spec/ext_spec.rb
67
84
  - spec/finder_spec.rb
68
85
  - spec/model_spec.rb
86
+ - spec/notification_spec.rb
69
87
  - spec/persistence_spec.rb
70
88
  - spec/relation_spec.rb