cdq 0.1.9 → 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
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