cdq 0.1.9 → 0.1.10

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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YTZiZDlhMzI5ODBiZWVmYjYyZWViNDA4YzhhODMyZDEwOTRlOWUyYQ==
4
+ YWY0OGVmY2Y5Y2UxMmNjZjI0YjcyODhmNmY0OWNhYWFkZmMwYjMzMQ==
5
5
  data.tar.gz: !binary |-
6
- MzBmOGI2MGQxY2RjYzFlZGNkMDdlNzg4NDc5MTAxY2QxZWIwODkzOA==
6
+ MTczM2QyOWI4NDczZDY1ODBlNDFjNDI0NGI1MjhjODFlNTU5Njc2Ng==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- MGUxZDhiODMyYjE5NjlkMzc1YTJjMTc0Mzg2MDc4NjhhNDAxOTk5OTRhMzg3
10
- MWU0NGVjODUyZDAzODNlZDlkYTllNTRhMjBmYzQ0Y2Q5ODBjYWU5MDFhNzVk
11
- MzEyMGFiNDQ2ODAyNjhiODBkZWI4YmUxNWRmOWNhYzhkYzBmZmU=
9
+ MmU3ZDJjMDZkOWE3YjczYzAxZDQ2MDM5NTBiNTc0YjY2OGQ5MDQzNWY5YmM1
10
+ MjZlMWY1MTBlYjRjNDVjZGI2NWFlYjQ0YzliYWYwYWM2NWE3ODc0Mjc5ZmQ5
11
+ OWY4MDExZmEwNzYzNGJiZGUzY2EwNmE0ZTQwZTliODQ4MWU4YjM=
12
12
  data.tar.gz: !binary |-
13
- ZjUwMmNlMzdjM2VkYjU2MWQyMzk1ZmY1ZTRiNWJiZDllYTZiZjJmM2Y0MDE1
14
- NmFlMTIyZTU4M2I2ZGY4NDljOTY2NTk4MWU4NzRmZGJjMmI4NjAxNDU4YjU5
15
- NDRmNDdlNDZkZDRmMjk1Mzc0OGNmMzZjZWE3OTY4MmM5M2Q5NzY=
13
+ YWY3YjliOGJiMDM1Y2JhZGZmMzQ2OGUxMDg3ODNjYzNkOWM0ODUxMGM4YzQ1
14
+ ZTA2N2FkNzhhODYxZTlmOGNjY2E4NzIxYjE0N2QwMjJjYmRlMzc4MWVmZWMz
15
+ NDBlZDhhYmMyZTI3ZTZiODllMGQ5MmJhZWIzNmJjYmRkYjgyYmU=
data/README.md CHANGED
@@ -322,6 +322,18 @@ defining and using named scopes:
322
322
  query generator for an entity, but `cdq(:attribute)` starts a predicate for an
323
323
  attribute.
324
324
 
325
+ ## iCloud
326
+
327
+ As of version 0.1.10, there is some experimental support for iCloud, written by
328
+ @katsuyoshi. Please try it out and let us know how it's working for you. To
329
+ enable, initialize like this:
330
+
331
+ ```ruby
332
+ cdq.stores.new(iCloud: true, container: "com.your.container.id")
333
+ ```
334
+
335
+ You can also set up iCloud in your cdq.yml file.
336
+
325
337
  ## Documentation
326
338
 
327
339
  * [API](http://rubydoc.info/github/infinitered/cdq)
data/lib/cdq.rb CHANGED
@@ -4,6 +4,7 @@ unless defined?(Motion::Project::App)
4
4
  end
5
5
 
6
6
  require 'ruby-xcdm'
7
+ require 'motion-yaml'
7
8
 
8
9
  ENV['COLUMNS'] ||= `tput cols`.strip
9
10
 
@@ -1,4 +1,4 @@
1
1
 
2
2
  module CDQ
3
- VERSION = '0.1.9'
3
+ VERSION = '0.1.10'
4
4
  end
@@ -51,9 +51,11 @@ module CDQ
51
51
  case obj
52
52
  when Class
53
53
  if obj.isSubclassOfClass(NSManagedObject)
54
+ entities = NSDictionary.dictionaryWithDictionary(
55
+ @@base_object.models.current.entitiesByName)
54
56
  entity_description =
55
- @@base_object.models.current.entitiesByName[obj.name] ||
56
- @@base_object.models.current.entitiesByName[obj.ancestors[1].name]
57
+ entities[obj.name] ||
58
+ entities[obj.ancestors[1].name]
57
59
  if entity_description.nil?
58
60
  raise "Cannot find an entity named #{obj.name}"
59
61
  end
@@ -62,7 +64,9 @@ module CDQ
62
64
  @@base_object
63
65
  end
64
66
  when String
65
- entity_description = @@base_object.models.current.entitiesByName[obj]
67
+ entities = NSDictionary.dictionaryWithDictionary(
68
+ @@base_object.models.current.entitiesByName)
69
+ entity_description = entities[obj]
66
70
  target_class = NSClassFromString(entity_description.managedObjectClassName)
67
71
  if entity_description.nil?
68
72
  raise "Cannot find an entity named #{obj}"
@@ -8,10 +8,12 @@ module CDQ
8
8
  # model file. This file is named <tt>cdq.yml</tt> and must be found at the
9
9
  # root of your resources directory. It supports the following top-level keys:
10
10
  #
11
- # [name] The root name for both database and model
12
- # [database_dir] The root name for the database directory (NSDocumentDirectory or NSApplicationSupportDirectory)
13
- # [database_name] The root name for the database file (relative to the database_dir)
14
- # [model_name] The root name for the model file (relative to the bundle directory)
11
+ # [name] The root name for both database and model
12
+ # [database_dir] The root name for the database directory (NSDocumentDirectory or NSApplicationSupportDirectory)
13
+ # [database_name] The root name for the database file (relative to the database_dir)
14
+ # [model_name] The root name for the model file (relative to the bundle directory)
15
+ # [icloud] If it's true, CDQ works with iCloud.
16
+ # [icloud_container] Set id of iCloud container if you use iCloud. If it's nil, use first container listed in the com.apple.developer.ubiquity-container-identifiers entitlement array.
15
17
  #
16
18
  # Using the config file is not necessary. If you do not include it, the bundle display name
17
19
  # will be used. For most people with a new app, this is what you want to do, especially if
@@ -22,27 +24,37 @@ module CDQ
22
24
  #
23
25
  class CDQConfig
24
26
 
25
- attr_reader :config_file, :database_name, :database_dir, :model_name, :name
27
+ attr_reader :config_file, :database_name, :database_dir, :model_name, :name, :icloud, :icloud_container
26
28
 
27
29
  def initialize(config_file)
30
+ h = nil
28
31
  case config_file
29
32
  when String
30
33
  @config_file = config_file
34
+ h = nil
31
35
  if File.file?(config_file)
32
36
  h = File.open(config_file) { |f| YAML.load(f.read) }
33
- else
34
- h = {}
37
+ # If a file was consisted comments only, it may parse as an Array.
38
+ h = nil unless h.is_a? Hash
35
39
  end
36
40
  when Hash
37
41
  h = config_file
38
- else
39
- h = {}
40
42
  end
43
+ h ||= {}
41
44
 
42
- @name = h['name'] || h[:name] || NSBundle.mainBundle.objectForInfoDictionaryKey("CFBundleDisplayName")
45
+ @name = h['name'] || h[:name] || NSBundle.mainBundle.objectForInfoDictionaryKey("CFBundleExecutable")
43
46
  @database_dir = search_directory_for h['database_dir'] || h[:database_dir]
44
47
  @database_name = h['database_name'] || h[:database_name] || name
45
48
  @model_name = h['model_name'] || h[:model_name] || name
49
+ @icloud = begin
50
+ case h['icloud'] || h[:icloud]
51
+ when true, 1
52
+ true
53
+ else
54
+ false
55
+ end
56
+ end
57
+ @icloud_container = h['icloud_container'] || h[:icloud_container]
46
58
  end
47
59
 
48
60
  def database_url
@@ -1,13 +1,18 @@
1
-
2
1
  module CDQ
3
2
 
4
3
  class CDQContextManager
5
4
 
6
5
  BACKGROUND_SAVE_NOTIFICATION = 'com.infinitered.cdq.context.background_save_completed'
6
+ DID_FINISH_IMPORT_NOTIFICATION = 'com.infinitered.cdq.context.did_finish_import'
7
7
 
8
8
  def initialize(opts = {})
9
9
  @store_manager = opts[:store_manager]
10
10
  end
11
+
12
+ def dealloc
13
+ NSNotificationCenter.defaultCenter.removeObserver(self) if @observed_context
14
+ super
15
+ end
11
16
 
12
17
  # Push a new context onto the stack for the current thread, making that context the
13
18
  # default. If a block is supplied, push for the duration of the block and then
@@ -66,6 +71,7 @@ module CDQ
66
71
  #
67
72
  def new(concurrency_type, &block)
68
73
  @has_been_set_up = true
74
+
69
75
  context = NSManagedObjectContext.alloc.initWithConcurrencyType(concurrency_type)
70
76
  if current
71
77
  context.parentContext = current
@@ -73,7 +79,15 @@ module CDQ
73
79
  if @store_manager.invalid?
74
80
  raise "store coordinator not found. Cannot create the first context without one."
75
81
  else
76
- context.persistentStoreCoordinator = @store_manager.current
82
+ context.mergePolicy = NSMergePolicy.alloc.initWithMergeType(NSMergeByPropertyObjectTrumpMergePolicyType)
83
+ context.performBlockAndWait ->{
84
+ coordinator = @store_manager.current
85
+ context.persistentStoreCoordinator = coordinator
86
+ #Dispatch::Queue.main.async {
87
+ NSNotificationCenter.defaultCenter.addObserver(self, selector:"did_finish_import:", name:NSPersistentStoreDidImportUbiquitousContentChangesNotification, object:nil)
88
+ @observed_context = context
89
+ #}
90
+ }
77
91
  end
78
92
  end
79
93
  push(context, &block)
@@ -82,6 +96,7 @@ module CDQ
82
96
  # Save all contexts in the stack, starting with the current and working down.
83
97
  #
84
98
  def save(options = {})
99
+ set_timestamps
85
100
  always_wait = options[:always_wait]
86
101
  stack.reverse.each do |context|
87
102
  if always_wait || context.concurrencyType == NSMainQueueConcurrencyType
@@ -125,6 +140,15 @@ module CDQ
125
140
  end
126
141
  true
127
142
  end
143
+
144
+
145
+ def did_finish_import(notification)
146
+ @observed_context.performBlockAndWait ->{
147
+ @observed_context.mergeChangesFromContextDidSaveNotification(notification)
148
+ NSNotificationCenter.defaultCenter.postNotificationName(DID_FINISH_IMPORT_NOTIFICATION, object:self, userInfo:{context: @observed_context})
149
+ }
150
+ end
151
+
128
152
 
129
153
  private
130
154
 
@@ -189,7 +213,16 @@ module CDQ
189
213
  end
190
214
  end
191
215
  end
216
+
217
+ def set_timestamps
218
+ 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=
222
+ e.updated_at = now if e.respond_to? :updated_at=
223
+ end
224
+ end
192
225
 
193
- end
226
+ end
194
227
 
195
228
  end
@@ -18,9 +18,9 @@ module CDQ
18
18
  end
19
19
 
20
20
  def reset!(opts = {})
21
- @@context_manager.reset!
21
+ @@context_manager.reset! if @@context_manager
22
22
  @@context_manager = nil
23
- @@store_manager.reset!
23
+ @@store_manager.reset! if @@store_manager
24
24
  @@store_manager = nil
25
25
  end
26
26
 
@@ -3,6 +3,8 @@ module CDQ
3
3
 
4
4
  class CDQStoreManager
5
5
 
6
+ STORE_DID_INITIALIZE_NOTIFICATION = 'com.infinitered.cdq.store.did_initialize'
7
+
6
8
  attr_writer :current
7
9
 
8
10
  def initialize(opts = {})
@@ -10,6 +12,13 @@ module CDQ
10
12
  @model_manager = opts[:model_manager]
11
13
  end
12
14
 
15
+ def new(opts = {})
16
+ @config = opts[:config] || CDQConfig.default
17
+ @model_manager = opts[:model_manager] || CDQ.cdq.models
18
+ @icloud = opts[:icloud] || opts[:iCloud] || @config.icloud
19
+ @icloud_container = @config.icloud_container
20
+ end
21
+
13
22
  def current
14
23
  @current ||= create_store
15
24
  end
@@ -28,27 +37,98 @@ module CDQ
28
37
  if invalid?
29
38
  raise "No model found. Can't create a persistent store coordinator without it."
30
39
  else
31
- coordinator = NSPersistentStoreCoordinator.alloc.initWithManagedObjectModel(@model_manager.current)
32
- error = Pointer.new(:object)
33
- options = { NSMigratePersistentStoresAutomaticallyOption => true,
34
- NSInferMappingModelAutomaticallyOption => true }
40
+ if @icloud
41
+ create_icloud_store
42
+ else
43
+ create_local_store
44
+ end
45
+ end
46
+ end
47
+
48
+ def create_icloud_store
49
+ coordinator = NSPersistentStoreCoordinator.alloc.initWithManagedObjectModel(@model_manager.current)
50
+
51
+ Dispatch::Queue.concurrent.async {
52
+ # get icloud first container
35
53
  url = @config.database_url
36
- mkdir_p File.dirname(url.path)
37
- store = coordinator.addPersistentStoreWithType(NSSQLiteStoreType,
38
- configuration:nil,
39
- URL:url,
40
- options:options,
41
- error:error)
42
- if store.nil?
43
- error[0].userInfo['metadata'] && error[0].userInfo['metadata'].each do |key, value|
44
- NSLog "#{key}: #{value}"
54
+ icloud_url = NSFileManager.defaultManager.URLForUbiquityContainerIdentifier(@icloud_container)
55
+ if icloud_url
56
+ error = Pointer.new(:object)
57
+ icloud_url = icloud_url.URLByAppendingPathComponent("data")
58
+ error = Pointer.new(:object)
59
+ options = { NSMigratePersistentStoresAutomaticallyOption => true,
60
+ NSInferMappingModelAutomaticallyOption => true,
61
+ NSPersistentStoreUbiquitousContentNameKey => url.path.lastPathComponent.gsub(".", "_"),
62
+ NSPersistentStoreUbiquitousContentURLKey => icloud_url,
63
+ }
64
+ coordinator.lock
65
+ store = coordinator.addPersistentStoreWithType(NSSQLiteStoreType,
66
+ configuration:nil,
67
+ URL:url,
68
+ options:options,
69
+ error:error)
70
+ coordinator.unlock
71
+
72
+ if store.nil?
73
+ error[0].userInfo['metadata'] && error[0].userInfo['metadata'].each do |key, value|
74
+ NSLog "#{key}: #{value}"
75
+ end
76
+ raise error[0].userInfo['reason']
77
+ end
78
+
79
+ else
80
+ error = Pointer.new(:object)
81
+ options = { NSMigratePersistentStoresAutomaticallyOption => true,
82
+ NSInferMappingModelAutomaticallyOption => true }
83
+ url = @config.database_url
84
+ mkdir_p File.dirname(url.path)
85
+ store = coordinator.addPersistentStoreWithType(NSSQLiteStoreType,
86
+ configuration:nil,
87
+ URL:url,
88
+ options:options,
89
+ error:error)
90
+ if store.nil?
91
+ error[0].userInfo['metadata'] && error[0].userInfo['metadata'].each do |key, value|
92
+ NSLog "#{key}: #{value}"
93
+ end
94
+ raise error[0].userInfo['reason']
45
95
  end
46
- raise error[0].userInfo['reason']
47
96
  end
48
- coordinator
97
+ Dispatch::Queue.main.after(0) {
98
+ # This block is executed in a next run loop.
99
+ # So the managed object context has a store coordinator in this point.
100
+ NSNotificationCenter.defaultCenter.postNotificationName(STORE_DID_INITIALIZE_NOTIFICATION, object:coordinator)
101
+ }
102
+ }
103
+ coordinator
104
+ end
105
+
106
+ def create_local_store
107
+ coordinator = NSPersistentStoreCoordinator.alloc.initWithManagedObjectModel(@model_manager.current)
108
+ error = Pointer.new(:object)
109
+ options = { NSMigratePersistentStoresAutomaticallyOption => true,
110
+ NSInferMappingModelAutomaticallyOption => true }
111
+ url = @config.database_url
112
+ mkdir_p File.dirname(url.path)
113
+ store = coordinator.addPersistentStoreWithType(NSSQLiteStoreType,
114
+ configuration:nil,
115
+ URL:url,
116
+ options:options,
117
+ error:error)
118
+ if store.nil?
119
+ error[0].userInfo['metadata'] && error[0].userInfo['metadata'].each do |key, value|
120
+ NSLog "#{key}: #{value}"
121
+ end
122
+ raise error[0].userInfo['reason']
49
123
  end
124
+ Dispatch::Queue.main.after(0) {
125
+ # This block is executed in a next run loop.
126
+ # So the managed object context has a store coordinator in this point.
127
+ NSNotificationCenter.defaultCenter.postNotificationName(STORE_DID_INITIALIZE_NOTIFICATION, object:coordinator)
128
+ }
129
+ coordinator
50
130
  end
51
-
131
+
52
132
  def mkdir_p dir
53
133
  error = Pointer.new(:object)
54
134
  m = NSFileManager.defaultManager
@@ -58,7 +138,7 @@ module CDQ
58
138
  raise error[0].localizedDescription
59
139
  end
60
140
  end
61
-
141
+
62
142
  end
63
143
 
64
144
  end
@@ -63,7 +63,11 @@ class CDQManagedObject < CoreDataQueryManagedObjectBase
63
63
  end
64
64
 
65
65
  def respond_to?(name)
66
- super || cdq.respond_to?(name)
66
+ if cdq_initialized?
67
+ super(name) || cdq.respond_to?(name)
68
+ else
69
+ super(name)
70
+ end
67
71
  end
68
72
 
69
73
  def destroy_all
@@ -77,6 +81,18 @@ class CDQManagedObject < CoreDataQueryManagedObjectBase
77
81
  cdq.save
78
82
  end
79
83
 
84
+ def cdq(obj = nil)
85
+ if obj
86
+ super(obj)
87
+ else
88
+ @cdq_object ||= super(nil)
89
+ end
90
+ end
91
+
92
+ def cdq_initialized?
93
+ !@cdq_object.nil?
94
+ end
95
+
80
96
  end
81
97
 
82
98
  # Register this object for destruction with the current context. Will not
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cdq
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - infinitered
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-06-13 00:00:00.000000000 Z
12
+ date: 2014-08-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ruby-xcdm
@@ -20,7 +20,7 @@ dependencies:
20
20
  version: '0.0'
21
21
  - - ! '>='
22
22
  - !ruby/object:Gem::Version
23
- version: 0.0.6
23
+ version: 0.0.7
24
24
  type: :runtime
25
25
  prerelease: false
26
26
  version_requirements: !ruby/object:Gem::Requirement
@@ -30,7 +30,7 @@ dependencies:
30
30
  version: '0.0'
31
31
  - - ! '>='
32
32
  - !ruby/object:Gem::Version
33
- version: 0.0.6
33
+ version: 0.0.7
34
34
  - !ruby/object:Gem::Dependency
35
35
  name: motion-yaml
36
36
  requirement: !ruby/object:Gem::Requirement