-
Notifications
You must be signed in to change notification settings - Fork 23
/
scope.rb
198 lines (165 loc) · 6.28 KB
/
scope.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
module MotionData
class Scope
include Enumerable
attr_reader :target, :predicate, :sortDescriptors
def initWithTarget(target)
initWithTarget(target, predicate:nil, sortDescriptors:nil)
end
def initWithTarget(target, predicate:predicate, sortDescriptors:sortDescriptors)
if init
@target, @predicate = target, predicate
@sortDescriptors = sortDescriptors ? sortDescriptors.dup : []
end
self
end
# Add finder conditions as a hash of requirements, a Scope, a NSPredicate,
# or a predicate format string with an optional list of arguments.
#
# The conditions are added using `AND`.
def where(conditions, *formatArguments)
predicate = case conditions
when Hash
NSCompoundPredicate.andPredicateWithSubpredicates(conditions.map do |keyPath, value|
Predicate::Builder.new(keyPath) == value
end)
when Scope
conditions.predicate
when NSPredicate
conditions
when String
NSPredicate.predicateWithFormat(conditions, argumentArray:formatArguments)
end
predicate = @predicate.and(predicate) if @predicate
scopeWithPredicate(predicate)
end
# Sort ascending by a key-path, or a NSSortDescriptor.
def sortBy(keyPathOrSortDescriptor)
if keyPathOrSortDescriptor.is_a?(NSSortDescriptor)
scopeByAddingSortDescriptor(keyPathOrSortDescriptor)
else
sortBy(keyPathOrSortDescriptor, ascending:true)
end
end
# Sort by a key-path.
def sortBy(keyPath, ascending:ascending)
scopeByAddingSortDescriptor(NSSortDescriptor.alloc.initWithKey(keyPath.to_s, ascending:ascending))
end
# Iterates over the array representation of the scope.
def each(&block)
array.each(&block)
end
# Factory methods that should be implemented by the subclass.
def array
raise "Not implemented"
end
alias_method :to_a, :array
def set
raise "Not implemented."
end
private
def scopeWithPredicate(predicate)
scopeWithPredicate(predicate, sortDescriptors:@sortDescriptors)
end
def scopeByAddingSortDescriptor(sortDescriptor)
sortDescriptors = @sortDescriptors.dup
sortDescriptors << sortDescriptor
scopeWithPredicate(@predicate, sortDescriptors:sortDescriptors)
end
def scopeWithPredicate(predicate, sortDescriptors:sortDescriptors)
self.class.alloc.initWithTarget(@target, predicate:predicate, sortDescriptors:sortDescriptors)
end
end
class Scope
class Set < Scope
def set
setByApplyingConditionsToSet(@target)
end
def array
set = self.set
set.is_a?(NSOrderedSet) ? set.array : set.allObjects
end
private
# Applies the finder and sort conditions and returns the result as a set.
def setByApplyingConditionsToSet(set)
if @predicate
if set.is_a?(NSOrderedSet)
# TODO not the most efficient way of doing this when there are also sort descriptors
filtered = set.array.filteredArrayUsingPredicate(@predicate)
set = NSOrderedSet.orderedSetWithArray(filtered)
else
set = set.filteredSetUsingPredicate(@predicate)
end
end
unless @sortDescriptors.empty?
set = set.set if set.is_a?(NSOrderedSet)
sorted = set.sortedArrayUsingDescriptors(@sortDescriptors)
set = NSOrderedSet.orderedSetWithArray(sorted)
end
set
end
end
end
class Scope
class Relationship < Scope::Set
attr_accessor :relationshipName, :owner, :ownerClass
def initWithTarget(target, relationshipName:relationshipName, owner:owner, ownerClass:ownerClass)
if initWithTarget(target)
@relationshipName, @owner, @ownerClass = relationshipName, owner, ownerClass
end
self
end
def new(properties = nil)
entity = targetClass.newInContext(@owner.managedObjectContext, properties)
# Uses the Core Data dynamically generated method to add objects to the relationship.
#
# E.g. if the relationship is called 'articles', then this will call: addArticles()
#
# TODO we currently use the one that takes a set instead of just one object, this is
# so we don't yet have to do any singularization
camelized = @relationshipName.to_s
camelized[0] = camelized[0,1].upcase
@owner.send("add#{camelized}", NSSet.setWithObject(entity))
entity
end
# Returns a NSFetchRequest with the current scope.
def fetchRequest
# Start with a predicate which selects those entities that belong to the owner.
predicate = Predicate::Builder.new(inverseRelationshipName) == @owner
# Then apply the scope's predicate.
predicate = predicate.and(@predicate) if @predicate
request = NSFetchRequest.new
request.entity = targetClass.entityDescription
request.predicate = predicate
request.sortDescriptors = @sortDescriptors unless @sortDescriptors.empty?
request
end
# Returns a NSFetchedResultsController with this fetch request.
def controller(options = {})
NSFetchedResultsController.alloc.initWithFetchRequest(self,
managedObjectContext:MotionData::Context.current,
sectionNameKeyPath:options[:sectionNameKeyPath],
cacheName:options[:cacheName])
end
private
def relationshipDescription
@ownerClass.entityDescription.relationshipsByName[@relationshipName]
end
def targetEntityDescription
relationshipDescription.destinationEntity
end
def targetClass
targetEntityDescription.klass
end
def inverseRelationshipName
relationshipDescription.inverseRelationship.name
end
def scopeWithPredicate(predicate, sortDescriptors:sortDescriptors)
scope = super
scope.relationshipName = @relationshipName
scope.owner = @owner
scope.ownerClass = @ownerClass
scope
end
end
end
end