cdq 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +11 -0
  3. data/Gemfile +7 -0
  4. data/Gemfile.lock +32 -0
  5. data/README.md +173 -0
  6. data/Rakefile +24 -0
  7. data/app/app_delegate.rb +13 -0
  8. data/app/test_models.rb +15 -0
  9. data/cdq.gemspec +18 -0
  10. data/lib/cdq.rb +12 -0
  11. data/lib/cdq/version.rb +4 -0
  12. data/motion/cdq.rb +58 -0
  13. data/motion/cdq/collection_proxy.rb +29 -0
  14. data/motion/cdq/config.rb +67 -0
  15. data/motion/cdq/context.rb +190 -0
  16. data/motion/cdq/model.rb +32 -0
  17. data/motion/cdq/object.rb +83 -0
  18. data/motion/cdq/object_proxy.rb +30 -0
  19. data/motion/cdq/partial_predicate.rb +53 -0
  20. data/motion/cdq/query.rb +128 -0
  21. data/motion/cdq/relationship_query.rb +122 -0
  22. data/motion/cdq/store.rb +52 -0
  23. data/motion/cdq/targeted_query.rb +170 -0
  24. data/motion/managed_object.rb +98 -0
  25. data/resources/CDQ.xcdatamodeld/.xccurrentversion +8 -0
  26. data/resources/CDQ.xcdatamodeld/0.0.1.xcdatamodel/contents +34 -0
  27. data/resources/Default-568h@2x.png +0 -0
  28. data/resources/KEEPME +0 -0
  29. data/schemas/001_baseline.rb +44 -0
  30. data/spec/cdq/collection_proxy_spec.rb +51 -0
  31. data/spec/cdq/config_spec.rb +74 -0
  32. data/spec/cdq/context_spec.rb +92 -0
  33. data/spec/cdq/managed_object_spec.rb +81 -0
  34. data/spec/cdq/model_spec.rb +14 -0
  35. data/spec/cdq/module_spec.rb +44 -0
  36. data/spec/cdq/object_proxy_spec.rb +37 -0
  37. data/spec/cdq/object_spec.rb +58 -0
  38. data/spec/cdq/partial_predicate_spec.rb +52 -0
  39. data/spec/cdq/query_spec.rb +127 -0
  40. data/spec/cdq/relationship_query_spec.rb +75 -0
  41. data/spec/cdq/store_spec.rb +39 -0
  42. data/spec/cdq/targeted_query_spec.rb +120 -0
  43. data/spec/helpers/thread_helper.rb +16 -0
  44. data/spec/integration_spec.rb +38 -0
  45. data/vendor/cdq/ext/CoreDataQueryManagedObjectBase.h +8 -0
  46. data/vendor/cdq/ext/CoreDataQueryManagedObjectBase.m +22 -0
  47. metadata +138 -0
@@ -0,0 +1,37 @@
1
+
2
+ module CDQ
3
+ describe "CDQ Object Proxy" do
4
+
5
+ before do
6
+ class << self
7
+ include CDQ
8
+ end
9
+
10
+ cdq.setup
11
+
12
+ @author = Author.create(name: "Stephen King")
13
+ @article = Article.create(title: "IT", author: @author)
14
+
15
+ @op = CDQObjectProxy.new(@author)
16
+ end
17
+
18
+ after do
19
+ cdq.reset!
20
+ end
21
+
22
+ it "wraps an NSManagedObject" do
23
+ @op.get.should == @author
24
+ end
25
+
26
+ it "wraps relations in CDQRelationshipQuery objects" do
27
+ @op.articles.class.should == CDQRelationshipQuery
28
+ @op.articles.first.should == @article
29
+ end
30
+
31
+ it "can delete the underlying object" do
32
+ @op.destroy
33
+ Author.count.should == 0
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,58 @@
1
+
2
+ module CDQ
3
+
4
+ describe "CDQ Object" do
5
+
6
+ before do
7
+ class << self
8
+ include CDQ
9
+ end
10
+
11
+ cdq.setup
12
+ end
13
+
14
+ after do
15
+ cdq.reset!
16
+ end
17
+
18
+ it "has a contexts method" do
19
+ cdq.contexts.class.should == CDQContextManager
20
+ end
21
+
22
+ it "has a stores method" do
23
+ cdq.stores.class.should == CDQStoreManager
24
+ end
25
+
26
+ it "has a models method" do
27
+ cdq.models.class.should == CDQModelManager
28
+ end
29
+
30
+ it "can override model" do
31
+ model = cdq.models.current
32
+
33
+ cdq.reset!
34
+
35
+ cdq.setup(model: model)
36
+ cdq.models.current.should == model
37
+ end
38
+
39
+ it "can override store" do
40
+ store = cdq.stores.current
41
+
42
+ cdq.reset!
43
+
44
+ cdq.setup(store: store)
45
+ cdq.stores.current.should == store
46
+ end
47
+
48
+ it "can override context" do
49
+ context = cdq.contexts.current
50
+
51
+ cdq.reset!
52
+
53
+ cdq.setup(context: context)
54
+ cdq.contexts.current.should == context
55
+ end
56
+ end
57
+
58
+ end
@@ -0,0 +1,52 @@
1
+
2
+ module CDQ
3
+ describe "CDQ Partial Predicates" do
4
+
5
+ before do
6
+ @scope = CDQQuery.new
7
+ @ppred = CDQPartialPredicate.new(:count, @scope)
8
+ end
9
+
10
+ it "is composed of a key symbol and a scope" do
11
+ @ppred.key.should == :count
12
+ @ppred.scope.should.not == nil
13
+ end
14
+
15
+ it "creates an equality predicate" do
16
+ scope = @ppred.eq(1)
17
+ scope.predicate.should == make_pred('count', NSEqualToPredicateOperatorType, 1)
18
+
19
+ scope = @ppred.equal(1)
20
+ scope.predicate.should == make_pred('count', NSEqualToPredicateOperatorType, 1)
21
+ end
22
+
23
+ it "creates a less-than predicate" do
24
+ scope = @ppred.lt(1)
25
+ scope.predicate.should == make_pred('count', NSLessThanPredicateOperatorType, 1)
26
+ end
27
+
28
+ it "preserves the previous scope" do
29
+ scope = CDQQuery.new(predicate: NSPredicate.predicateWithValue(false))
30
+ ppred = CDQPartialPredicate.new(:count, scope)
31
+ ppred.eq(1).predicate.should == NSCompoundPredicate.andPredicateWithSubpredicates(
32
+ [NSPredicate.predicateWithValue(false), make_pred('count', NSEqualToPredicateOperatorType, 1)]
33
+ )
34
+ end
35
+
36
+ it "works with 'or' too" do
37
+ scope = CDQQuery.new(predicate: NSPredicate.predicateWithValue(true))
38
+ ppred = CDQPartialPredicate.new(:count, scope, :or)
39
+ ppred.eq(1).predicate.should == NSCompoundPredicate.orPredicateWithSubpredicates(
40
+ [NSPredicate.predicateWithValue(true), make_pred('count', NSEqualToPredicateOperatorType, 1)]
41
+ )
42
+ end
43
+ def make_pred(key, type, value, options = 0)
44
+ NSComparisonPredicate.predicateWithLeftExpression(
45
+ NSExpression.expressionForKeyPath(key.to_s),
46
+ rightExpression:NSExpression.expressionForConstantValue(value),
47
+ modifier:NSDirectPredicateModifier,
48
+ type:type,
49
+ options:options)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,127 @@
1
+
2
+ module CDQ
3
+ describe "CDQ Query" do
4
+
5
+ before do
6
+ @query = CDQQuery.new
7
+ end
8
+
9
+ it "creates a query with a simple true predicate" do
10
+ @query.predicate.should == nil
11
+ @query.limit.should == nil
12
+ @query.offset.should == nil
13
+ end
14
+
15
+ it "can set a limit on a query" do
16
+ @query = CDQQuery.new(limit: 1)
17
+ @query.limit.should == 1
18
+ @query.offset.should == nil
19
+ end
20
+
21
+ it "can set a offset on a query" do
22
+ @query = CDQQuery.new(offset: 1)
23
+ @query.limit.should == nil
24
+ @query.offset.should == 1
25
+ end
26
+
27
+ it "can 'and' itself with another query" do
28
+ @query = CDQQuery.new(limit: 1, offset: 1)
29
+ @other = CDQQuery.new(predicate: NSPredicate.predicateWithValue(false), limit: 2)
30
+ @compound = @query.and(@other)
31
+ @compound.predicate.should == NSPredicate.predicateWithValue(false)
32
+ @compound.limit.should == 2
33
+ @compound.offset.should == 1
34
+ end
35
+
36
+ it "can 'and' itself with an NSPredicate" do
37
+ @compound = @query.and(NSPredicate.predicateWithValue(false))
38
+ @compound.predicate.should == NSPredicate.predicateWithValue(false)
39
+ end
40
+
41
+ it "can 'and' itself with a string-based predicate query" do
42
+ query = @query.where(:name).begins_with('foo')
43
+ compound = query.and("name != %@", 'fool')
44
+ compound.predicate.predicateFormat.should == 'name BEGINSWITH "foo" AND name != "fool"'
45
+ end
46
+
47
+ it "can 'and' itself with a hash" do
48
+ compound = @query.and(name: "foo", fee: 2)
49
+ compound.predicate.predicateFormat.should == 'name == "foo" AND fee == 2'
50
+ end
51
+
52
+ it "starts a partial predicate when 'and'-ing a symbol" do
53
+ ppred = @query.and(:name)
54
+ ppred.class.should == CDQPartialPredicate
55
+ ppred.key.should == :name
56
+ end
57
+
58
+ it "can 'or' itself with another query" do
59
+ @query = CDQQuery.new(limit: 1, offset: 1)
60
+ @other = CDQQuery.new(predicate: NSPredicate.predicateWithValue(false), limit: 2)
61
+ @compound = @query.or(@other)
62
+ @compound.predicate.should == NSPredicate.predicateWithValue(false)
63
+ @compound.limit.should == 2
64
+ @compound.offset.should == 1
65
+ end
66
+
67
+ it "can 'or' itself with an NSPredicate" do
68
+ @compound = @query.or(NSPredicate.predicateWithValue(false))
69
+ @compound.predicate.should == NSPredicate.predicateWithValue(false)
70
+ end
71
+
72
+ it "can 'or' itself with a string-based predicate query" do
73
+ query = @query.where(:name).begins_with('foo')
74
+ compound = query.or("name != %@", 'fool')
75
+ compound.predicate.predicateFormat.should == 'name BEGINSWITH "foo" OR name != "fool"'
76
+ end
77
+
78
+ it "can sort by a key" do
79
+ @query.sort_by(:name).sort_descriptors.should == [
80
+ NSSortDescriptor.sortDescriptorWithKey('name', ascending: true)
81
+ ]
82
+ end
83
+
84
+ it "can sort descending" do
85
+ @query.sort_by(:name, :desc).sort_descriptors.should == [
86
+ NSSortDescriptor.sortDescriptorWithKey('name', ascending: false)
87
+ ]
88
+ end
89
+
90
+ it "can chain sorts" do
91
+ @query.sort_by(:name).sort_by(:title).sort_descriptors.should == [
92
+ NSSortDescriptor.sortDescriptorWithKey('name', ascending: true),
93
+ NSSortDescriptor.sortDescriptorWithKey('title', ascending: true)
94
+ ]
95
+ end
96
+
97
+ it "reuses the previous key when calling 'and' or 'or' with no arguments" do
98
+ compound = @query.where(:name).begins_with('foo').and.ne('fool')
99
+ compound.predicate.predicateFormat.should == 'name BEGINSWITH "foo" AND name != "fool"'
100
+
101
+ compound = @query.where(:name).begins_with('foo').or.eq('loofa')
102
+ compound.predicate.predicateFormat.should == 'name BEGINSWITH "foo" OR name == "loofa"'
103
+ end
104
+
105
+ it "handles complex examples" do
106
+ query1 = CDQQuery.new
107
+ query2 = query1.where(CDQQuery.new.where(:name).ne('bob', NSCaseInsensitivePredicateOption).or(:amount).gt(42).sort_by(:name))
108
+ query3 = query1.where(CDQQuery.new.where(:enabled).eq(true).and(:'job.title').ne(nil).sort_by(:amount, :desc))
109
+
110
+ query4 = query3.where(query2)
111
+ query4.predicate.predicateFormat.should == '(enabled == 1 AND job.title != nil) AND (name !=[c] "bob" OR amount > 42)'
112
+ query4.sort_descriptors.should == [
113
+ NSSortDescriptor.alloc.initWithKey('amount', ascending:false),
114
+ NSSortDescriptor.alloc.initWithKey('name', ascending:true)
115
+ ]
116
+ end
117
+
118
+ it "can make a new query with a new limit" do:w
119
+ @query = CDQQuery.new
120
+ new_query = @query.limit(1)
121
+
122
+ new_query.limit.should == 1
123
+ new_query.offset.should == nil
124
+ end
125
+
126
+ end
127
+ end
@@ -0,0 +1,75 @@
1
+
2
+ module CDQ
3
+
4
+ describe "CDQ Relationship Query" do
5
+
6
+ before do
7
+
8
+ class << self
9
+ include CDQ
10
+ end
11
+
12
+ cdq.setup
13
+
14
+ @author = Author.create(name: "eecummings")
15
+ @article1 = @author.articles.create(author: @author, body: "", published: true, publishedAt: Time.local(1922), title: "The Enormous Room")
16
+
17
+ cdq.save(always_wait: true)
18
+
19
+ end
20
+
21
+ after do
22
+ cdq.reset!
23
+ end
24
+
25
+ it "performs queries against the target entity" do
26
+ @rq = CDQRelationshipQuery.new(@author, 'articles')
27
+ @rq.first.should != nil
28
+ @rq.first.class.should == Article_Article_
29
+ end
30
+
31
+ it "should be able to use named scopes" do
32
+ cdq(@author).articles.all_published.array.should == [@article1]
33
+ end
34
+
35
+ it "can handle many-to-many correctly" do
36
+ ram = Writer.create(name: "Ram Das")
37
+ first = ram.spouses.create
38
+ second = ram.spouses.create
39
+ ram.spouses.array.should == [first, second]
40
+ cdq(first).writers.array.should == [ram]
41
+ cdq(second).writers.array.should == [ram]
42
+ cdq(first).writers.where(:name).contains("o").array.should == []
43
+ cdq(first).writers.where(:name).contains("a").array.should == [ram]
44
+ end
45
+
46
+ it "can add objects to the relationship" do
47
+ article = Article.create(body: "bank")
48
+ @author.articles.add(article)
49
+ @author.articles.where(body: "bank").first.should == article
50
+ article.author.should == @author
51
+
52
+ ram = Writer.create(name: "Ram Das")
53
+ ram.spouses.add cdq('Spouse').create
54
+ ram.spouses << cdq('Spouse').create
55
+
56
+ ram.spouses.count.should == 2
57
+ ram.spouses.first.writers.count.should == 1
58
+
59
+ end
60
+
61
+ it "iterates over ordered sets correctly" do
62
+ writer = Writer.create
63
+ two = cdq('Spouse').create(name: "1")
64
+ three = cdq('Spouse').create(name: "2")
65
+ one = writer.spouses.create(name: "3")
66
+ writer.spouses << two
67
+ writer.spouses << three
68
+ writer.spouses.map(&:name).should == ["3", "1", "2"]
69
+ writer.spouses.array.map(&:name).should == ["3", "1", "2"]
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+
@@ -0,0 +1,39 @@
1
+
2
+ module CDQ
3
+
4
+ describe "CDQ Store Manager" do
5
+
6
+ before do
7
+ CDQ.cdq.setup
8
+ @sm = CDQStoreManager.new(model_manager: CDQ.cdq.models)
9
+ end
10
+
11
+ after do
12
+ CDQ.cdq.reset!
13
+ end
14
+
15
+ it "can set up a store coordinator with default name" do
16
+ @sm.current.should != nil
17
+ @sm.current.class.should == NSPersistentStoreCoordinator
18
+ end
19
+
20
+ it "rejects attempt to create without a valid model" do
21
+ c = CDQConfig.new(name: "foo")
22
+ mm = CDQModelManager.new(config: c)
23
+ sm = CDQStoreManager.new(config: c, model_manager: mm)
24
+ should.raise do
25
+ sm.current
26
+ end
27
+ end
28
+
29
+ it "permits setting custom store manager" do
30
+ nsm = CDQStoreManager.new(model: nil)
31
+ should.not.raise do
32
+ nsm.current = @sm.current
33
+ nsm.current
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -0,0 +1,120 @@
1
+
2
+ module CDQ
3
+ describe "CDQ Targeted Query" do
4
+
5
+ before do
6
+ CDQ.cdq.setup
7
+ end
8
+
9
+ after do
10
+ CDQ.cdq.reset!
11
+ end
12
+
13
+ it "reflects a base state" do
14
+ tq = CDQTargetedQuery.new(Author.entity_description, Author)
15
+ tq.count.should == 0
16
+ tq.array.should == []
17
+ end
18
+
19
+ it "can count objects" do
20
+ tq = CDQTargetedQuery.new(Author.entity_description, Author)
21
+ Author.create(name: "eecummings")
22
+ tq.count.should == 1
23
+ Author.create(name: "T. S. Eliot")
24
+ tq.count.should == 2
25
+ end
26
+
27
+ it "can fetch objects" do
28
+ tq = CDQTargetedQuery.new(Author.entity_description, Author)
29
+ eecummings = Author.create(name: "eecummings")
30
+ tseliot = Author.create(name: "T. S. Eliot")
31
+ tq.array.sort_by(&:name).should == [tseliot, eecummings]
32
+ end
33
+
34
+ it "can create objects" do
35
+ tq = CDQTargetedQuery.new(Author.entity_description, Author)
36
+ maya = tq.create(name: "maya angelou")
37
+ tq.where(:name).eq("maya angelou").first.should == maya
38
+ end
39
+
40
+ end
41
+
42
+ describe "CDQ Targeted Query with data" do
43
+
44
+ before do
45
+ CDQ.cdq.setup
46
+
47
+ class << self
48
+ include CDQ
49
+ end
50
+
51
+ @tq = cdq(Author)
52
+ @eecummings = Author.create(name: "eecummings")
53
+ @tseliot = Author.create(name: "T. S. Eliot")
54
+ @dante = Author.create(name: "dante")
55
+ cdq.save
56
+ end
57
+
58
+ after do
59
+ CDQ.cdq.reset!
60
+ end
61
+
62
+ it "performs a sorted fetch" do
63
+ @tq.sort_by(:name).array.should == [@tseliot, @dante, @eecummings]
64
+ end
65
+
66
+ it "performs a limited fetch" do
67
+ @tq.sort_by(:name).limit(1).array.should == [@tseliot]
68
+ end
69
+
70
+ it "performs an offset fetch" do
71
+ @tq.sort_by(:name).offset(1).array.should == [@dante, @eecummings]
72
+ @tq.sort_by(:name).offset(1).limit(1).array.should == [@dante]
73
+ end
74
+
75
+ it "performs a restricted search" do
76
+ @tq.where(:name).eq("dante").array.should == [@dante]
77
+ end
78
+
79
+ it "gets the first entry" do
80
+ @tq.sort_by(:name).first.should == @tseliot
81
+ end
82
+
83
+ it "gets entries by index" do
84
+ @tq.sort_by(:name)[0].should == @tseliot
85
+ @tq.sort_by(:name)[1].should == @dante
86
+ @tq.sort_by(:name)[2].should == @eecummings
87
+ end
88
+
89
+ it "can iterate over entries" do
90
+ entries = [@tseliot, @dante, @eecummings]
91
+
92
+ @tq.sort_by(:name).each do |e|
93
+ e.should == entries.shift
94
+ end
95
+ end
96
+
97
+ it "can map over entries" do
98
+ entries = [@tseliot, @dante, @eecummings]
99
+
100
+ @tq.sort_by(:name).map { |e| e }.should == entries
101
+ end
102
+
103
+ it "can create a named scope" do
104
+ @tq.scope :two_sorted_by_name, @tq.sort_by(:name).limit(2)
105
+ @tq.two_sorted_by_name.array.should == [@tseliot, @dante]
106
+ end
107
+
108
+ it "can create a dynamic named scope" do
109
+ tq = cdq(Article)
110
+ a = tq.create(publishedAt: Time.local(2001))
111
+ b = tq.create(publishedAt: Time.local(2002))
112
+ c = tq.create(publishedAt: Time.local(2003))
113
+ d = tq.create(publishedAt: Time.local(2004))
114
+
115
+ tq.scope :date_span { |start_date, end_date| cdq(:publishedAt).lt(end_date).and.ge(start_date) }
116
+ tq.date_span(Time.local(2002), Time.local(2004)).sort_by(:publishedAt).array.should == [b, c]
117
+ Article.published_since(Time.local(2003)).sort_by(:publishedAt).array.should == [c, d]
118
+ end
119
+ end
120
+ end