cdq 0.1.11 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -17,7 +17,8 @@ XCode, or you can use [ruby-xcdm](https://github.com/infinitered/ruby-xcdm).
17
17
 
18
18
  ## Introducing CDQ
19
19
 
20
- CDQ began its life as a fork of [MotionData](https://github.com/alloy/MotionData), but it became obvious I
20
+ CDQ began its life as a fork of
21
+ [MotionData](https://github.com/alloy/MotionData), but it became obvious I
21
22
  wanted to take things in a different direction, so I cut loose and ended up
22
23
  rewriting almost everything. If you pay attention, you can still find the
23
24
  genetic traces, so thanks to @alloy for sharing his work and letting me learn
@@ -28,7 +29,7 @@ avoiding too much abstraction or method pollution on top of the SDK. While it
28
29
  borrows many ideas from ActiveRecord (especially AREL), it is designed to
29
30
  harmonize with Core Data's way of doing things first.
30
31
 
31
- I am actively developing and improving CDQ (updated September 2014) so if you have
32
+ I am actively developing and improving CDQ (updated February 2015) so if you have
32
33
  trouble or find a bug, please open a ticket!
33
34
 
34
35
  ### Why use a static Data Model?
@@ -46,14 +47,14 @@ $ cd my_app
46
47
  $ cdq init
47
48
  ```
48
49
 
49
- This way assumes you want to use ruby-xcdm. Run ```cdq -h``` for list of more generators.
50
+ This way assumes you want to use ruby-xcdm. Run `cdq -h` for list of more generators.
50
51
 
51
52
  ### Using Bundler:
52
53
 
53
54
  ```ruby
54
55
  gem 'cdq'
55
56
  ```
56
- <br />
57
+
57
58
  If you want to see bleeding-edge changes, point Bundler at the git repo:
58
59
 
59
60
  ```ruby
@@ -67,7 +68,7 @@ it to your resources file and make sure it's named the same as your RubyMotion
67
68
  project. If you're using `ruby-xcdm` (which I highly recommend) then it will
68
69
  create the datamodel file automatically and put it in the right place.
69
70
 
70
- Now include the setup code in your ```app_delegate.rb``` file:
71
+ Now include the setup code in your `app_delegate.rb` file:
71
72
 
72
73
  ```ruby
73
74
  class AppDelegate
@@ -129,22 +130,25 @@ about contexts at all.
129
130
  For a great discussion of why you might want to use nested contexts, see [here](http://www.cocoanetics.com/2012/07/multi-context-coredata/).
130
131
 
131
132
  CDQ maintains a stack of contexts (one stack per thread), and by default, all
132
- operations on objects use the topmost context. You just call ```cdq.save```
133
+ operations on objects use the topmost context. You just call `cdq.save`
133
134
  and it saves the whole stack. Or you can get a list of all the contexts in
134
- order with ```cdq.contexts.all``` and do more precise work.
135
+ order with `cdq.contexts.all` and do more precise work.
135
136
 
136
137
  Settings things up the way you want is easy. Here's how you'd set it up for asynchronous
137
138
  saves:
138
139
 
139
140
  ```ruby
140
- cdq.contexts.new(NSPrivateQueueConcurrencyType)
141
- cdq.contexts.new(NSMainQueueConcurrencyType)
141
+ cdq.contexts.push(:root)
142
+ cdq.contexts.push(:main)
142
143
  ```
143
144
 
144
145
  This pushes a private queue context onto the bottom of the stack, then a main queue context on top of it.
145
- Since the main queue is on top, all your data operations will use that. ```cdq.save``` then saves the
146
+ Since the main queue is on top, all your data operations will use that. `cdq.save` then saves the
146
147
  main context, and schedules a save on the root context.
147
148
 
149
+ In addition, since these two contexts are globally important, it makes them available at `cdq.contexts.main` and
150
+ `cdq.contexts.root`.
151
+
148
152
  ### Temporary Contexts
149
153
 
150
154
  From time to time, you may need to use a temporary context. For example, on
@@ -153,8 +157,11 @@ load into a temporary context (possibly in a background thread) and then move
153
157
  all the data over to your main context all at once. CDQ makes that easy too:
154
158
 
155
159
  ```ruby
156
- cdq.contexts.new(NSConfinementConcurrencyType) do
160
+ cdq.background do
161
+
157
162
  # Your work here
163
+
164
+ cdq.save
158
165
  end
159
166
  ```
160
167
 
@@ -247,12 +254,12 @@ Here are some examples. **See the [cheat sheet](https://github.com/infinitered/
247
254
  Like ActiveRecord, CDQ will not run a fetch until you actually request specific
248
255
  objects. There are several methods for getting at the data:
249
256
 
250
- * ```array```
251
- * ```first```
252
- * ```each```
253
- * ```[]```
254
- * ```map```
255
- * Anything else in ```Enumerable```
257
+ * `array`
258
+ * `first`
259
+ * `each`
260
+ * `[]`
261
+ * `map`
262
+ * Anything else in `Enumerable`
256
263
 
257
264
  ## Dedicated Models
258
265
 
@@ -299,12 +306,12 @@ name, You'll need to use a <tt>cdq.yml</tt> config file. See
299
306
  ### Working without model classes using the master method
300
307
 
301
308
  If you need or want to work without using CDQManagedObject as your base class,
302
- you can use the ```cdq()``` master method. This is a "magic" method, like
303
- ```rmq()``` in [RubyMotionQuery](http://github.com/infinitered/rmq) or
304
- ```$()``` in jQuery, which will lift whatever you pass into it into the CDQ
309
+ you can use the `cdq()`master method. This is a "magic" method, like
310
+ `rmq()` in [RubyMotionQuery](http://github.com/infinitered/rmq) or
311
+ `$()` in jQuery, which will lift whatever you pass into it into the CDQ
305
312
  universe. The method is available inside all UIResponder classes (so, views and
306
313
  controllers) as well as in the console. You can use it anywhere else by
307
- including the model ```CDQ``` into your classes. To use an entity without a
314
+ including the model `CDQ` into your classes. To use an entity without a
308
315
  model class, just pass its name as a string into the master method, like so
309
316
 
310
317
  ```ruby
@@ -1,4 +1,4 @@
1
1
 
2
2
  module CDQ
3
- VERSION = '0.1.11'
3
+ VERSION = '1.0.0'
4
4
  end
@@ -2,13 +2,15 @@ module CDQ
2
2
 
3
3
  class CDQContextManager
4
4
 
5
+ include Deprecation
6
+
5
7
  BACKGROUND_SAVE_NOTIFICATION = 'com.infinitered.cdq.context.background_save_completed'
6
8
  DID_FINISH_IMPORT_NOTIFICATION = 'com.infinitered.cdq.context.did_finish_import'
7
9
 
8
10
  def initialize(opts = {})
9
11
  @store_manager = opts[:store_manager]
10
12
  end
11
-
13
+
12
14
  def dealloc
13
15
  NSNotificationCenter.defaultCenter.removeObserver(self) if @observed_context
14
16
  super
@@ -18,12 +20,18 @@ module CDQ
18
20
  # default. If a block is supplied, push for the duration of the block and then
19
21
  # return to the previous state.
20
22
  #
21
- def push(context, &block)
23
+ def push(context, options = {}, &block)
22
24
  @has_been_set_up = true
25
+
26
+ unless context.is_a? NSManagedObjectContext
27
+ context = create(context, options)
28
+ end
29
+
23
30
  if block_given?
24
31
  save_stack do
25
- push_to_stack(context)
32
+ context = push_to_stack(context)
26
33
  block.call
34
+ context
27
35
  end
28
36
  else
29
37
  push_to_stack(context)
@@ -38,6 +46,7 @@ module CDQ
38
46
  save_stack do
39
47
  rval = pop_from_stack
40
48
  block.call
49
+ rval
41
50
  end
42
51
  else
43
52
  pop_from_stack
@@ -48,7 +57,7 @@ module CDQ
48
57
  #
49
58
  def current
50
59
  if stack.empty? && !@has_been_set_up
51
- new(NSMainQueueConcurrencyType)
60
+ push(NSMainQueueConcurrencyType)
52
61
  end
53
62
  stack.last
54
63
  end
@@ -69,13 +78,41 @@ module CDQ
69
78
  # will be set to the previous head context. If a block is supplied, the new context
70
79
  # will exist for the duration of the block and then the previous state will be restore_managerd.
71
80
  #
81
+ # REMOVE1.1
82
+ #
72
83
  def new(concurrency_type, &block)
84
+ deprecate "cdq.contexts.new() is deprecated. Use push() or create()"
85
+ context = create(concurrency_type)
86
+ push(context, {}, &block)
87
+ end
88
+
89
+ # Create a new context by type, setting upstream to the topmost context if available,
90
+ # or to the persistent store coordinator if not. Return the context but do NOT push it
91
+ # onto the stack.
92
+ #
93
+ # Options:
94
+ #
95
+ # :named - Assign the context a name, making it available as cdq.contexts.<name>. The
96
+ # name is permanent, and should only be used for contexts that are intended to be global,
97
+ # since the object will never get released.
98
+ #
99
+ def create(concurrency_type, options = {}, &block)
73
100
  @has_been_set_up = true
74
-
75
- context = NSManagedObjectContext.alloc.initWithConcurrencyType(concurrency_type)
76
- if current
77
- context.parentContext = current
101
+
102
+ case concurrency_type
103
+ when :main
104
+ context = NSManagedObjectContext.alloc.initWithConcurrencyType(NSMainQueueConcurrencyType)
105
+ options[:named] = :main unless options.has_key?(:named)
106
+ when :private_queue, :private
107
+ context = NSManagedObjectContext.alloc.initWithConcurrencyType(NSPrivateQueueConcurrencyType)
108
+ when :root
109
+ context = NSManagedObjectContext.alloc.initWithConcurrencyType(NSPrivateQueueConcurrencyType)
110
+ options[:named] = :root unless options.has_key?(:named)
78
111
  else
112
+ context = NSManagedObjectContext.alloc.initWithConcurrencyType(concurrency_type)
113
+ end
114
+
115
+ if stack.empty?
79
116
  if @store_manager.invalid?
80
117
  raise "store coordinator not found. Cannot create the first context without one."
81
118
  else
@@ -89,17 +126,62 @@ module CDQ
89
126
  #}
90
127
  }
91
128
  end
129
+ else
130
+ context.parentContext = stack.last
92
131
  end
93
- push(context, &block)
132
+
133
+ if options[:named]
134
+ if respond_to?(options[:named])
135
+ raise "Cannot name a context '#{options[:named]}': conflicts with existing method"
136
+ end
137
+ self.class.send(:define_method, options[:named]) do
138
+ context
139
+ end
140
+ end
141
+ context
94
142
  end
95
143
 
96
- # Save all contexts in the stack, starting with the current and working down.
144
+ # Save all passed contexts in order. If none are supplied, save all
145
+ # contexts in the stack, starting with the current and working down. If
146
+ # you pass a symbol instead of a context, it will look up context with
147
+ # that name.
97
148
  #
98
- def save(options = {})
149
+ # Options:
150
+ #
151
+ # always_wait: If true, force use of performBlockAndWait for synchronous
152
+ # saves. By default, private queue saves are performed asynchronously.
153
+ # Main queue saves are always synchronous if performed from the main
154
+ # queue.
155
+ #
156
+ def save(*contexts_and_options)
157
+
158
+ if contexts_and_options.last.is_a? Hash
159
+ options = contexts_and_options.pop
160
+ else
161
+ options = {}
162
+ end
163
+
164
+ if contexts_and_options.empty?
165
+ contexts = stack.reverse
166
+ else
167
+ # resolve named contexts
168
+ contexts = contexts_and_options.map do |c|
169
+ if c.is_a? Symbol
170
+ send(c)
171
+ else
172
+ c
173
+ end
174
+ end
175
+ end
176
+
99
177
  set_timestamps
100
178
  always_wait = options[:always_wait]
101
- stack.reverse.each do |context|
102
- if always_wait || context.concurrencyType == NSMainQueueConcurrencyType
179
+ contexts.each do |context|
180
+ if context.concurrencyType == NSMainQueueConcurrencyType && NSThread.isMainThread
181
+ with_error_object do |error|
182
+ context.save(error)
183
+ end
184
+ elsif always_wait
103
185
  context.performBlockAndWait( -> {
104
186
 
105
187
  with_error_object do |error|
@@ -140,7 +222,51 @@ module CDQ
140
222
  end
141
223
  true
142
224
  end
143
-
225
+
226
+ # Run the supplied block in a new context with a private queue. Once the
227
+ # block exits, the context will be forgotten, so any changes made must be
228
+ # saved within the block.
229
+ #
230
+ # Note that the CDQ context stack, which is used when deciding what to save
231
+ # with `cdq.save` is stored per-thread, so the stack inside the block is
232
+ # different from the stack outside the block. If you push any more contexts
233
+ # inside, they will also disappear when the thread terminates.
234
+ #
235
+ # The thread is also unique. If you call `background` multiple times, it will
236
+ # be a different thread each time with no persisted state.
237
+ #
238
+ # Options:
239
+ # wait: If true, run the block synchronously
240
+ #
241
+ def background(options = {}, &block)
242
+ # Create a new private queue context with the main context as its parent
243
+ context = create(NSPrivateQueueConcurrencyType)
244
+
245
+ on(context, options) do
246
+ push(context, {}, &block)
247
+ end
248
+
249
+ end
250
+
251
+ # Run a block on the supplied context using performBlock. If context is a
252
+ # symbol, it will look up the corresponding named context and use that
253
+ # instead.
254
+ #
255
+ # Options:
256
+ # wait: If true, run the block synchronously
257
+ #
258
+ def on(context, options = {}, &block)
259
+
260
+ if context.is_a? Symbol
261
+ context = send(context)
262
+ end
263
+
264
+ if options[:wait]
265
+ context.performBlockAndWait(block)
266
+ else
267
+ context.performBlock(block)
268
+ end
269
+ end
144
270
 
145
271
  def did_finish_import(notification)
146
272
  @observed_context.performBlockAndWait ->{
@@ -213,14 +339,19 @@ module CDQ
213
339
  end
214
340
  end
215
341
  end
216
-
342
+
217
343
  def set_timestamps
218
344
  now = Time.now
219
- eos = current.insertedObjects.allObjects + current.updatedObjects.allObjects
220
- eos.each do |e|
221
- e.created_at ||= now if e.respond_to? :created_at=
345
+
346
+ current.insertedObjects.allObjects.each do |e|
347
+ e.created_at = now if e.respond_to? :created_at=
222
348
  e.updated_at = now if e.respond_to? :updated_at=
223
349
  end
350
+
351
+ current.updatedObjects.allObjects.each do |e|
352
+ e.updated_at = now if e.respond_to? :updated_at=
353
+ end
354
+
224
355
  end
225
356
 
226
357
  end
@@ -0,0 +1,13 @@
1
+
2
+ module CDQ
3
+ module Deprecation
4
+
5
+ class << self
6
+ attr_accessor :silence_deprecation
7
+ end
8
+
9
+ def deprecate(message)
10
+ puts message unless CDQ::Deprecation.silence_deprecation
11
+ end
12
+ end
13
+ end
@@ -33,7 +33,7 @@ module CDQ
33
33
  elsif opts[:model]
34
34
  models.current = opts[:model]
35
35
  end
36
- contexts.new(NSMainQueueConcurrencyType)
36
+ contexts.push(NSMainQueueConcurrencyType)
37
37
  true
38
38
  end
39
39
 
@@ -41,6 +41,10 @@ module CDQ
41
41
  contexts.save(*args)
42
42
  end
43
43
 
44
+ def background(*args, &block)
45
+ contexts.background(*args, &block)
46
+ end
47
+
44
48
  def find(oid)
45
49
  url = NSURL.URLWithString(oid)
46
50
  object_id = stores.current.managedObjectIDForURIRepresentation(url)
@@ -0,0 +1,52 @@
1
+
2
+ module CDQ
3
+ class CDQObserver
4
+
5
+ include CDQ
6
+
7
+ def initialize(actions, klass_or_object, &block)
8
+ actions = Array(actions)
9
+ keys = actions.map { |a| key_for_action(a) }
10
+
11
+ if klass_or_object.is_a?(Class)
12
+ test = proc { |obj| obj.is_a?(klass_or_object) }
13
+ else
14
+ test = proc { |obj| obj == klass_or_object }
15
+ end
16
+
17
+ @ns_observer = App.notification_center.observe NSManagedObjectContextDidSaveNotification, cdq.contexts.current do |notif|
18
+
19
+ actions.zip(keys).each do |action, key|
20
+
21
+ objects = notif.userInfo[key]
22
+ if objects
23
+ objects.allObjects.select(&test).each do |obj|
24
+ if block.arity == 2
25
+ block.call(obj, action)
26
+ else
27
+ block.call(obj)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ def ns_observer
36
+ @ns_observer
37
+ end
38
+
39
+ def key_for_action(action)
40
+ case action
41
+ when :insert
42
+ NSInsertedObjectsKey
43
+ when :update
44
+ NSUpdatedObjectsKey
45
+ when :delete
46
+ NSDeletedObjectsKey
47
+ else
48
+ raise ArgumentError.new("No such action #{action.inspect}")
49
+ end
50
+ end
51
+ end
52
+ end
metadata CHANGED
@@ -1,7 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cdq
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.11
4
+ version: 1.0.0
5
+ prerelease:
5
6
  platform: ruby
6
7
  authors:
7
8
  - infinitered
@@ -9,11 +10,12 @@ authors:
9
10
  autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
- date: 2014-11-22 00:00:00.000000000 Z
13
+ date: 2015-02-19 00:00:00.000000000 Z
13
14
  dependencies:
14
15
  - !ruby/object:Gem::Dependency
15
16
  name: ruby-xcdm
16
17
  requirement: !ruby/object:Gem::Requirement
18
+ none: false
17
19
  requirements:
18
20
  - - ~>
19
21
  - !ruby/object:Gem::Version
@@ -24,6 +26,7 @@ dependencies:
24
26
  type: :runtime
25
27
  prerelease: false
26
28
  version_requirements: !ruby/object:Gem::Requirement
29
+ none: false
27
30
  requirements:
28
31
  - - ~>
29
32
  - !ruby/object:Gem::Version
@@ -34,6 +37,7 @@ dependencies:
34
37
  - !ruby/object:Gem::Dependency
35
38
  name: motion-yaml
36
39
  requirement: !ruby/object:Gem::Requirement
40
+ none: false
37
41
  requirements:
38
42
  - - ! '>='
39
43
  - !ruby/object:Gem::Version
@@ -41,6 +45,7 @@ dependencies:
41
45
  type: :runtime
42
46
  prerelease: false
43
47
  version_requirements: !ruby/object:Gem::Requirement
48
+ none: false
44
49
  requirements:
45
50
  - - ! '>='
46
51
  - !ruby/object:Gem::Version
@@ -62,9 +67,11 @@ files:
62
67
  - motion/cdq/collection_proxy.rb
63
68
  - motion/cdq/config.rb
64
69
  - motion/cdq/context.rb
70
+ - motion/cdq/deprecation.rb
65
71
  - motion/cdq/model.rb
66
72
  - motion/cdq/object.rb
67
73
  - motion/cdq/object_proxy.rb
74
+ - motion/cdq/observer.rb
68
75
  - motion/cdq/partial_predicate.rb
69
76
  - motion/cdq/query.rb
70
77
  - motion/cdq/relationship_query.rb
@@ -82,25 +89,32 @@ files:
82
89
  homepage: http://infinitered.com/cdq
83
90
  licenses:
84
91
  - MIT
85
- metadata: {}
86
92
  post_install_message:
87
93
  rdoc_options: []
88
94
  require_paths:
89
95
  - lib
90
96
  required_ruby_version: !ruby/object:Gem::Requirement
97
+ none: false
91
98
  requirements:
92
99
  - - ! '>='
93
100
  - !ruby/object:Gem::Version
94
101
  version: '0'
102
+ segments:
103
+ - 0
104
+ hash: -1573090934011425202
95
105
  required_rubygems_version: !ruby/object:Gem::Requirement
106
+ none: false
96
107
  requirements:
97
108
  - - ! '>='
98
109
  - !ruby/object:Gem::Version
99
110
  version: '0'
111
+ segments:
112
+ - 0
113
+ hash: -1573090934011425202
100
114
  requirements: []
101
115
  rubyforge_project:
102
- rubygems_version: 2.1.5
116
+ rubygems_version: 1.8.23
103
117
  signing_key:
104
- specification_version: 4
118
+ specification_version: 3
105
119
  summary: A streamlined library for working with Core Data outside XCode
106
120
  test_files: []
checksums.yaml DELETED
@@ -1,15 +0,0 @@
1
- ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- MzdmYTFjN2U5ZTg1ZmExOTM3ODM3NGU0MDE3M2E3NTBmYjNmNTU5NQ==
5
- data.tar.gz: !binary |-
6
- YTA3Nzc3YmFiZGZkNjE0Njk2NGFkNmUwOTU5OWFiZjFiNTY3Y2IzMw==
7
- SHA512:
8
- metadata.gz: !binary |-
9
- Y2FkMTlkNmNmZGRiZjc2Y2ViNjlhOTBhY2I5YjA5MDFhZGQ5YjMxYmMxMTVj
10
- MWRjZGE5NmVjOGUzNzE5YWFkODhjNjAxNDVhOWEwZDQwMGVmY2E2NDVlNTVl
11
- YWM2Y2EzYzU4MzA3MmY0YTliODhkNTdhYWJkNDhlYjdhNjMwYzE=
12
- data.tar.gz: !binary |-
13
- ZDM3Y2Y2MWQwZTRhYWIzMWEyZjZjZTFjMzI2MmM1ZmM0MjliYzU3YjQwYWZk
14
- NDM3MTFhZDIxYzNjNmRhNmZiYTA3MzE4ZjA0YmJlOTY0MTMxNDZhMzU5Yjdk
15
- MzkwYjU5OGQ1YzM3MDM3OWI2YmRhMjQ1ZjAwNTUzMmI1YjQ3NGQ=