├── .gitignore
├── Example
├── SampleProject
│ ├── en.lproj
│ │ └── InfoPlist.strings
│ ├── Models
│ │ ├── Categories
│ │ │ ├── Car+Mappings.h
│ │ │ ├── Person+Mappings.h
│ │ │ ├── InsuranceCompany+Mappings.h
│ │ │ ├── Person+Mappings.m
│ │ │ ├── InsuranceCompany+Mappings.m
│ │ │ └── Car+Mappings.m
│ │ ├── Car.m
│ │ ├── InsuranceCompany.m
│ │ ├── OBRPerson.m
│ │ ├── OBRPerson.h
│ │ ├── Car.h
│ │ ├── Person.m
│ │ ├── InsuranceCompany.h
│ │ └── Person.h
│ ├── SampleProject.xcdatamodeld
│ │ ├── .xccurrentversion
│ │ └── SampleProject.xcdatamodel
│ │ │ └── contents
│ ├── AppDelegate.h
│ ├── main.m
│ ├── SampleProject-Prefix.pch
│ ├── AppDelegate.m
│ └── SampleProject-Info.plist
├── SampleProjectTests
│ ├── en.lproj
│ │ └── InfoPlist.strings
│ ├── SampleProjectTests-Info.plist
│ ├── CoreDataManagerTests.m
│ ├── MappingsTests.m
│ └── FindersAndCreatorsTests.m
├── Default-568h@2x.png
├── Podfile
├── SampleProject.xcworkspace
│ └── contents.xcworkspacedata
├── .gitignore
├── Makefile
└── SampleProject.xcodeproj
│ ├── xcshareddata
│ └── xcschemes
│ │ └── SampleProject.xcscheme
│ └── project.pbxproj
├── .travis.yml
├── CHANGELOG.md
├── ObjectiveRecord.podspec
├── LICENSE
├── Classes
├── ObjectiveRecord.h
├── CoreDataManager.h
├── NSManagedObject+Mappings.h
├── NSManagedObject+ActiveRecord.h
├── NSManagedObject+Mappings.m
├── CoreDataManager.m
└── NSManagedObject+ActiveRecord.m
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | Podfile.lock
3 |
--------------------------------------------------------------------------------
/Example/SampleProject/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/Example/SampleProjectTests/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/Example/Default-568h@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supermarin/ObjectiveRecord/HEAD/Example/Default-568h@2x.png
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 |
3 | before_install:
4 | - cd Example
5 |
6 | install: make install
7 |
8 | script: make ci
9 |
10 |
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, :deployment_target => "5.0"
2 | pod 'ObjectiveSugar'
3 | pod 'ObjectiveRecord', :path => '../'
4 | target :SampleProjectTests, :exclusive => true do
5 | pod 'Kiwi/XCTest', '2.2.4'
6 | end
7 |
--------------------------------------------------------------------------------
/Example/SampleProject.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Example/SampleProject/Models/Categories/Car+Mappings.h:
--------------------------------------------------------------------------------
1 | //
2 | // Car+Mappings.h
3 | // SampleProject
4 | //
5 | // Created by Marin Usalj on 5/31/13.
6 | //
7 | //
8 |
9 | #import "Car.h"
10 | @class Person;
11 | @interface Car (Mappings)
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/Example/SampleProject/Models/Categories/Person+Mappings.h:
--------------------------------------------------------------------------------
1 | //
2 | // Person+Mappings.h
3 | // SampleProject
4 | //
5 | // Created by Marin Usalj on 5/31/13.
6 | //
7 | //
8 |
9 | #import "Person.h"
10 |
11 | @interface Person (Mappings)
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/Example/SampleProject/Models/Categories/InsuranceCompany+Mappings.h:
--------------------------------------------------------------------------------
1 | //
2 | // InsuranceCompany+Mappings.h
3 | // SampleProject
4 | //
5 | // Created by Marin Usalj on 12/3/13.
6 | //
7 | //
8 |
9 | #import "InsuranceCompany.h"
10 |
11 | @interface InsuranceCompany (Mappings)
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/Example/SampleProject/SampleProject.xcdatamodeld/.xccurrentversion:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | _XCCurrentVersionName
6 | SampleProject.xcdatamodel
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/SampleProject/Models/Car.m:
--------------------------------------------------------------------------------
1 | //
2 | // Car.m
3 | // SampleProject
4 | //
5 | // Created by Marin Usalj on 12/3/13.
6 | //
7 | //
8 |
9 | #import "Car.h"
10 | #import "InsuranceCompany.h"
11 | #import "Person.h"
12 |
13 |
14 | @implementation Car
15 |
16 | @dynamic horsePower;
17 | @dynamic make;
18 | @dynamic owner;
19 | @dynamic insuranceCompany;
20 |
21 | @end
22 |
--------------------------------------------------------------------------------
/Example/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | build/*
3 | *.pbxuser
4 | !default.pbxuser
5 | *.mode1v3
6 | !default.mode1v3
7 | *.mode2v3
8 | !default.mode2v3
9 | *.perspectivev3
10 | !default.perspectivev3
11 | !default.xcworkspace
12 | xcuserdata
13 | *.xccheckout
14 | profile
15 | *.moved-aside
16 | .idea
17 | Profiles_and_certificates
18 | Pods
19 |
20 | xcodebuild.log
21 | xcodebuild_error.log
22 |
--------------------------------------------------------------------------------
/Example/SampleProject/AppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.h
3 | // SampleProject
4 | //
5 | // Created by Marin Usalj on 7/4/12.
6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface AppDelegate : UIResponder
12 |
13 | @property (strong, nonatomic) UIWindow *window;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/Example/SampleProject/Models/InsuranceCompany.m:
--------------------------------------------------------------------------------
1 | //
2 | // InsuranceCompany.m
3 | // SampleProject
4 | //
5 | // Created by Delisa Mason on 12/27/13.
6 | //
7 | //
8 |
9 | #import "InsuranceCompany.h"
10 | #import "Car.h"
11 | #import "Person.h"
12 |
13 |
14 | @implementation InsuranceCompany
15 |
16 | @dynamic name;
17 | @dynamic remoteID;
18 | @dynamic cars;
19 | @dynamic owner;
20 |
21 | @end
22 |
--------------------------------------------------------------------------------
/Example/SampleProject/Models/OBRPerson.m:
--------------------------------------------------------------------------------
1 | //
2 | // OBRPerson.m
3 | // SampleProject
4 | //
5 | // Created by Elliot Neal on 22/05/2013.
6 | //
7 | //
8 |
9 | #import "OBRPerson.h"
10 |
11 |
12 | @implementation OBRPerson
13 |
14 | @dynamic name;
15 | @dynamic surname;
16 | @dynamic age;
17 | @dynamic isMember;
18 |
19 |
20 | + (NSString *)entityName {
21 | return @"OtherPerson";
22 | }
23 |
24 | @end
25 |
--------------------------------------------------------------------------------
/Example/SampleProject/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // SampleProject
4 | //
5 | // Created by Marin Usalj on 7/4/12.
6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #import "AppDelegate.h"
12 |
13 | int main(int argc, char *argv[])
14 | {
15 | @autoreleasepool {
16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Example/SampleProject/SampleProject-Prefix.pch:
--------------------------------------------------------------------------------
1 | //
2 | // Prefix header for all source files of the 'SampleProject' target in the 'SampleProject' project
3 | //
4 |
5 | #import
6 |
7 | #ifndef __IPHONE_3_0
8 | #warning "This project uses features only available in iOS SDK 3.0 and later."
9 | #endif
10 |
11 | #ifdef __OBJC__
12 | #import
13 | #import
14 | #import
15 | #import "ObjectiveRecord.h"
16 | #endif
17 |
--------------------------------------------------------------------------------
/Example/SampleProject/Models/OBRPerson.h:
--------------------------------------------------------------------------------
1 | //
2 | // OBRPerson.h
3 | // SampleProject
4 | //
5 | // Created by Elliot Neal on 22/05/2013.
6 | //
7 | //
8 |
9 | #import
10 | #import
11 |
12 |
13 | @interface OBRPerson : NSManagedObject
14 |
15 | @property (nonatomic, retain) NSString * name;
16 | @property (nonatomic, retain) NSString * surname;
17 | @property (nonatomic, retain) NSNumber * age;
18 | @property (nonatomic, retain) NSNumber * isMember;
19 |
20 | @end
21 |
--------------------------------------------------------------------------------
/Example/SampleProject/Models/Car.h:
--------------------------------------------------------------------------------
1 | //
2 | // Car.h
3 | // SampleProject
4 | //
5 | // Created by Marin Usalj on 12/3/13.
6 | //
7 | //
8 |
9 | #import
10 | #import
11 |
12 | @class InsuranceCompany, Person;
13 |
14 | @interface Car : NSManagedObject
15 |
16 | @property (nonatomic, retain) NSNumber * horsePower;
17 | @property (nonatomic, retain) NSString * make;
18 | @property (nonatomic, retain) Person *owner;
19 | @property (nonatomic, retain) InsuranceCompany *insuranceCompany;
20 |
21 | @end
22 |
--------------------------------------------------------------------------------
/Example/SampleProject/Models/Person.m:
--------------------------------------------------------------------------------
1 | //
2 | // Person.m
3 | // SampleProject
4 | //
5 | // Created by Delisa Mason on 12/27/13.
6 | //
7 | //
8 |
9 | #import "Person.h"
10 | #import "Car.h"
11 | #import "InsuranceCompany.h"
12 |
13 |
14 | @implementation Person
15 |
16 | @dynamic age;
17 | @dynamic anniversary;
18 | @dynamic firstName;
19 | @dynamic isMember;
20 | @dynamic lastName;
21 | @dynamic remoteID;
22 | @dynamic savings;
23 | @dynamic cars;
24 | @dynamic employees;
25 | @dynamic manager;
26 | @dynamic insuranceCompany;
27 |
28 | @end
29 |
--------------------------------------------------------------------------------
/Example/Makefile:
--------------------------------------------------------------------------------
1 | PROJECT_NAME=SampleProject
2 | WORKSPACE=$(PROJECT_NAME).xcworkspace
3 | DEFAULT_SCHEME=$(PROJECT_NAME)
4 | DEFAULT_FLAGS=-sdk iphonesimulator
5 | TESTING_TOOL=xcodebuild
6 |
7 | DEFAULT_TASK=$(TESTING_TOOL) -workspace $(WORKSPACE) -scheme $(DEFAULT_SCHEME) $(DEFAULT_FLAGS)
8 |
9 | clean:
10 | $(DEFAULT_TASK) clean
11 |
12 | test:
13 | $(DEFAULT_TASK) test | xcpretty -tc
14 |
15 | install:
16 | gem install cocoapods --no-ri --no-rdoc
17 | gem install xcpretty --no-ri --no-rdoc
18 | pod install
19 |
20 | ci:
21 | $(DEFAULT_TASK) test | xcpretty -c
22 |
23 |
--------------------------------------------------------------------------------
/Example/SampleProject/Models/Categories/Person+Mappings.m:
--------------------------------------------------------------------------------
1 |
2 | // Person+Mappings.m
3 | // SampleProject
4 | //
5 | // Created by Marin Usalj on 5/31/13.
6 | //
7 | //
8 |
9 | #import "Person+Mappings.h"
10 | #import "Car+Mappings.h"
11 |
12 | @implementation Person (Mappings)
13 |
14 | + (NSString *)primaryKey {
15 | return @"remoteID";
16 | }
17 |
18 | + (NSDictionary *)mappings {
19 | return @{
20 | @"id" : [self primaryKey],
21 | @"employees": @{ @"class": [Person class] },
22 | @"cars" : @{ @"class": [Car class] },
23 | @"manager" : @{ @"class": [Person class] }
24 | };
25 | }
26 |
27 | @end
28 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.5
2 |
3 | #### Breaking changes
4 |
5 | - `+mappings` are now a class method!
6 |
7 | - Added support for variadic arguments in `+where:`, `+find:`, and
8 | `+countWhere:`. (This also removes the now-redundant `+whereFormat:`.)
9 |
10 | #### Enhancements
11 |
12 | - Formatted arguments now use `+predicateWithFormat:` instead of
13 | `+stringWithFormat:` (to enforce proper predicate quoting).
14 |
15 | - `+find:` now supports predicates.
16 |
17 |
18 | ## 1.2
19 |
20 | #### Features
21 |
22 | - Added model mappings. Vanilla snake_case to camelCase happens automatically.
23 | For custom keys, override `-mappings` in your `NSManagedObject` subclass.
24 |
--------------------------------------------------------------------------------
/Example/SampleProject/Models/Categories/InsuranceCompany+Mappings.m:
--------------------------------------------------------------------------------
1 | //
2 | // InsuranceCompany+Mappings.m
3 | // SampleProject
4 | //
5 | // Created by Marin Usalj on 12/3/13.
6 | //
7 | //
8 |
9 | #import "InsuranceCompany+Mappings.h"
10 | #import "Person.h"
11 |
12 | @implementation InsuranceCompany (Mappings)
13 |
14 | + (id)primaryKey {
15 | return @"remoteID";
16 | }
17 |
18 | + (NSDictionary *)mappings {
19 | return @{
20 | @"id" : [self primaryKey],
21 | @"owner_id" : @{
22 | @"key" : @"owner",
23 | @"class" : [Person class] },
24 | @"owner" : @{
25 | @"class" : [Person class] }
26 | };
27 | }
28 |
29 | @end
30 |
--------------------------------------------------------------------------------
/Example/SampleProject/Models/InsuranceCompany.h:
--------------------------------------------------------------------------------
1 | //
2 | // InsuranceCompany.h
3 | // SampleProject
4 | //
5 | // Created by Delisa Mason on 12/27/13.
6 | //
7 | //
8 |
9 | #import
10 | #import
11 |
12 | @class Car, Person;
13 |
14 | @interface InsuranceCompany : NSManagedObject
15 |
16 | @property (nonatomic, retain) NSString * name;
17 | @property (nonatomic, retain) NSNumber * remoteID;
18 | @property (nonatomic, retain) NSSet *cars;
19 | @property (nonatomic, retain) Person *owner;
20 | @end
21 |
22 | @interface InsuranceCompany (CoreDataGeneratedAccessors)
23 |
24 | - (void)addCarsObject:(Car *)value;
25 | - (void)removeCarsObject:(Car *)value;
26 | - (void)addCars:(NSSet *)values;
27 | - (void)removeCars:(NSSet *)values;
28 |
29 | @end
30 |
--------------------------------------------------------------------------------
/ObjectiveRecord.podspec:
--------------------------------------------------------------------------------
1 | @version = "1.5.0"
2 |
3 | Pod::Spec.new do |s|
4 | s.name = "ObjectiveRecord"
5 | s.version = @version
6 | s.summary = "Lightweight and sexy Core Data finders, creators and other methods. Rails syntax."
7 | s.homepage = "https://github.com/supermarin/ObjectiveRecord"
8 | s.license = { :type => 'MIT', :file => 'LICENSE' }
9 |
10 | s.author = { "Marin Usalj" => "mneorr@gmail.com" }
11 | s.source = { :git => "https://github.com/supermarin/ObjectiveRecord.git", :tag => @version }
12 |
13 | s.source_files = 'Classes/**/*.{h,m}'
14 | s.framework = 'CoreData'
15 | s.requires_arc = true
16 |
17 | s.ios.deployment_target = '4.0'
18 | s.osx.deployment_target = '10.6'
19 |
20 | s.dependency 'ObjectiveSugar'
21 | end
22 |
--------------------------------------------------------------------------------
/Example/SampleProject/Models/Categories/Car+Mappings.m:
--------------------------------------------------------------------------------
1 | //
2 | // Car+Mappings.m
3 | // SampleProject
4 | //
5 | // Created by Marin Usalj on 5/31/13.
6 | //
7 | //
8 |
9 | #import "Car+Mappings.h"
10 | #import "Person+Mappings.h"
11 | #import "InsuranceCompany.h"
12 |
13 | @implementation Car (Mappings)
14 |
15 | + (NSDictionary *)mappings {
16 | return @{
17 | @"hp": @"horsePower",
18 | @"owner": @{
19 | @"class": [Person class]
20 | },
21 | @"insurance_id": @{
22 | @"key": @"insuranceCompany",
23 | @"class": [InsuranceCompany class]
24 | },
25 | @"insurance_company": @{
26 | @"key": @"insuranceCompany",
27 | @"class": [InsuranceCompany class]
28 | }
29 |
30 | };
31 | }
32 |
33 | @end
34 |
--------------------------------------------------------------------------------
/Example/SampleProjectTests/SampleProjectTests-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | com.mneorr.${PRODUCT_NAME:rfc1034identifier}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundlePackageType
14 | BNDL
15 | CFBundleShortVersionString
16 | 1.0
17 | CFBundleSignature
18 | ????
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Example/SampleProject/AppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.m
3 | // SampleProject
4 | //
5 | // Created by Marin Usalj on 7/4/12.
6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved.
7 | //
8 |
9 | #import "AppDelegate.h"
10 | #import "CoreDataManager.h"
11 |
12 | @implementation AppDelegate
13 |
14 | @synthesize window = _window;
15 |
16 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
17 | {
18 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
19 | // Override point for customization after application launch.
20 | self.window.backgroundColor = [UIColor whiteColor];
21 | [self.window makeKeyAndVisible];
22 | return YES;
23 | }
24 |
25 |
26 | - (void)applicationWillTerminate:(UIApplication *)application
27 | {
28 | // Saves changes in the application's managed object context before the application terminates.
29 | [[CoreDataManager sharedManager] saveContext];
30 | }
31 |
32 |
33 | @end
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Marin Usalj
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Classes/ObjectiveRecord.h:
--------------------------------------------------------------------------------
1 | // ObjectiveRecord.h
2 | //
3 | // Copyright (c) 2014 Marin Usalj
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 |
23 | #import "NSManagedObject+ActiveRecord.h"
24 | #import "NSManagedObject+Mappings.h"
25 |
--------------------------------------------------------------------------------
/Example/SampleProject/Models/Person.h:
--------------------------------------------------------------------------------
1 | //
2 | // Person.h
3 | // SampleProject
4 | //
5 | // Created by Delisa Mason on 12/27/13.
6 | //
7 | //
8 |
9 | #import
10 | #import
11 |
12 | @class Car, InsuranceCompany, Person;
13 |
14 | @interface Person : NSManagedObject
15 |
16 | @property (nonatomic, retain) NSNumber * age;
17 | @property (nonatomic, retain) NSDate * anniversary;
18 | @property (nonatomic, retain) NSString * firstName;
19 | @property (nonatomic, retain) NSNumber * isMember;
20 | @property (nonatomic, retain) NSString * lastName;
21 | @property (nonatomic, retain) NSNumber * remoteID;
22 | @property (nonatomic, retain) NSNumber * savings;
23 | @property (nonatomic, retain) NSSet *cars;
24 | @property (nonatomic, retain) NSSet *employees;
25 | @property (nonatomic, retain) Person *manager;
26 | @property (nonatomic, retain) InsuranceCompany *insuranceCompany;
27 | @end
28 |
29 | @interface Person (CoreDataGeneratedAccessors)
30 |
31 | - (void)addCarsObject:(Car *)value;
32 | - (void)removeCarsObject:(Car *)value;
33 | - (void)addCars:(NSSet *)values;
34 | - (void)removeCars:(NSSet *)values;
35 |
36 | - (void)addEmployeesObject:(Person *)value;
37 | - (void)removeEmployeesObject:(Person *)value;
38 | - (void)addEmployees:(NSSet *)values;
39 | - (void)removeEmployees:(NSSet *)values;
40 |
41 | @end
42 |
--------------------------------------------------------------------------------
/Example/SampleProject/SampleProject-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | ${PRODUCT_NAME}
9 | CFBundleExecutable
10 | ${EXECUTABLE_NAME}
11 | CFBundleIdentifier
12 | com.mneorr.${PRODUCT_NAME:rfc1034identifier}
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | ${PRODUCT_NAME}
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1.0
25 | LSRequiresIPhoneOS
26 |
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/Classes/CoreDataManager.h:
--------------------------------------------------------------------------------
1 | // CoreDataManager.h
2 | //
3 | // Copyright (c) 2014 Marin Usalj
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 |
23 | #import
24 | #import
25 |
26 | @interface CoreDataManager : NSObject
27 |
28 | @property (readonly, nonatomic) NSManagedObjectContext *managedObjectContext;
29 | @property (readonly, nonatomic) NSManagedObjectModel *managedObjectModel;
30 | @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
31 |
32 | @property (copy, nonatomic) NSString *databaseName;
33 | @property (copy, nonatomic) NSString *modelName;
34 |
35 | + (id)instance DEPRECATED_ATTRIBUTE;
36 | + (instancetype)sharedManager;
37 |
38 | - (BOOL)saveContext;
39 | - (void)useInMemoryStore;
40 |
41 | #pragma mark - Helpers
42 |
43 | - (NSURL *)applicationDocumentsDirectory;
44 | - (NSURL *)applicationSupportDirectory;
45 |
46 | @end
47 |
--------------------------------------------------------------------------------
/Example/SampleProjectTests/CoreDataManagerTests.m:
--------------------------------------------------------------------------------
1 | #import "Kiwi.h"
2 | #import
3 |
4 | void resetCoreDataStack(CoreDataManager *manager) {
5 | [manager setValue:nil forKey:@"persistentStoreCoordinator"];
6 | [manager setValue:nil forKey:@"managedObjectContext"];
7 | [manager setValue:nil forKey:@"managedObjectModel"];
8 | }
9 |
10 | SPEC_BEGIN(CoreDataManagerTests)
11 |
12 | describe(@"Core data stack", ^{
13 |
14 | CoreDataManager *manager = [CoreDataManager new];
15 |
16 | afterEach(^{
17 | resetCoreDataStack(manager);
18 | });
19 |
20 | it(@"can use in-memory store", ^{
21 | [manager useInMemoryStore];
22 | NSPersistentStore *store = [manager.persistentStoreCoordinator persistentStores][0];
23 | [[store.type should] equal:NSInMemoryStoreType];
24 | });
25 |
26 | it(@"uses documents directory on iphone", ^{
27 | [manager stub:@selector(isOSX) andReturn:theValue(NO)];
28 | NSPersistentStore *store = manager.persistentStoreCoordinator.persistentStores[0];
29 | [[store.URL.absoluteString should] containString:[manager applicationDocumentsDirectory].absoluteString];
30 | });
31 |
32 | it(@"uses application support directory on osx", ^{
33 | [manager stub:@selector(isOSX) andReturn:theValue(YES)];
34 | NSPersistentStore *store = manager.persistentStoreCoordinator.persistentStores[0];
35 | [[store.URL.absoluteString should] containString:[manager applicationSupportDirectory].absoluteString];
36 | });
37 |
38 | it(@"creates application support directory on OSX if needed", ^{
39 | [manager stub:@selector(isOSX) andReturn:theValue(YES)];
40 | [[NSFileManager defaultManager] removeItemAtURL:manager.applicationSupportDirectory error:nil];
41 |
42 | NSPersistentStore *store = [manager.persistentStoreCoordinator persistentStores][0];
43 | [[store.URL.absoluteString should] endWithString:@".sqlite"];
44 | });
45 |
46 | });
47 |
48 | SPEC_END
49 |
--------------------------------------------------------------------------------
/Classes/NSManagedObject+Mappings.h:
--------------------------------------------------------------------------------
1 | // NSManagedObject+Mappings.h
2 | //
3 | // Copyright (c) 2014 Marin Usalj
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 |
23 | #import
24 |
25 | @interface NSManagedObject (Mappings)
26 |
27 | /**
28 | A dictionary mapping remote (server) attribute names to local (Core Data) attribute names. Optionally overridden in NSManagedObject subclasses.
29 |
30 | @return A dictionary.
31 | */
32 | + (NSDictionary *)mappings;
33 |
34 | /**
35 | Returns a Core Data attribute name for a remote attribute name. Returns values defined in @c +mappings or, by default, converts snake case to camel case (e.g., @c @@"first_name" becomes @c @@"firstName").
36 |
37 | @see +[NSManagedObject mappings]
38 |
39 | @param key A remote (server) attribute name.
40 | @param context A local managed object context.
41 |
42 | @return A local (Core Data) attribute name.
43 | */
44 | + (NSString *)keyForRemoteKey:(NSString *)remoteKey inContext:(NSManagedObjectContext *)context;
45 |
46 | /**
47 | Transforms a given object for a remote attribute name.
48 |
49 | @param value Object to be transformed (e.g., a dictionary may become a managed object)
50 | @param remoteKey A remote (server) attribute name.
51 | @param context A local managed object context.
52 |
53 | @return A tranformed object.
54 | */
55 | + (id)transformValue:(id)value forRemoteKey:(NSString *)remoteKey inContext:(NSManagedObjectContext *)context;
56 |
57 | /**
58 | The keypath uniquely identifying your entity. Usually an ID, e.g., @c @@"remoteID".
59 |
60 | @return An attribute name.
61 | */
62 | + (NSString *)primaryKey;
63 |
64 | @end
65 |
--------------------------------------------------------------------------------
/Classes/NSManagedObject+ActiveRecord.h:
--------------------------------------------------------------------------------
1 | // NSManagedObject+ActiveRecord.h
2 | //
3 | // Copyright (c) 2014 Marin Usalj
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 |
23 | #import
24 | #import "NSManagedObject+Mappings.h"
25 | #import "CoreDataManager.h"
26 |
27 | @interface NSManagedObjectContext (ActiveRecord)
28 |
29 | /**
30 | The default context (as defined on the @c CoreDataManager singleton).
31 |
32 | @see -[CoreDataManager managedObjectContext]
33 |
34 | @return A managed object context.
35 | */
36 | + (NSManagedObjectContext *)defaultContext;
37 |
38 | @end
39 |
40 | @interface NSManagedObject (ActiveRecord)
41 |
42 |
43 | #pragma mark - Default Context
44 |
45 | - (BOOL)save;
46 | - (void)delete;
47 | + (void)deleteAll;
48 |
49 | + (id)create;
50 | + (id)create:(NSDictionary *)attributes;
51 | - (void)update:(NSDictionary *)attributes;
52 |
53 | + (NSArray *)all;
54 | + (NSArray *)allWithOrder:(id)order;
55 | + (NSArray *)where:(id)condition, ...;
56 | + (NSArray *)where:(id)condition order:(id)order;
57 | + (NSArray *)where:(id)condition limit:(NSNumber *)limit;
58 | + (NSArray *)where:(id)condition order:(id)order limit:(NSNumber *)limit;
59 | + (instancetype)findOrCreate:(NSDictionary *)attributes;
60 | + (instancetype)find:(id)condition, ...;
61 | + (NSUInteger)count;
62 | + (NSUInteger)countWhere:(id)condition, ...;
63 |
64 | #pragma mark - Custom Context
65 |
66 | + (id)createInContext:(NSManagedObjectContext *)context;
67 | + (id)create:(NSDictionary *)attributes inContext:(NSManagedObjectContext *)context;
68 |
69 | + (void)deleteAllInContext:(NSManagedObjectContext *)context;
70 |
71 | + (NSArray *)allInContext:(NSManagedObjectContext *)context;
72 | + (NSArray *)allInContext:(NSManagedObjectContext *)context order:(id)order;
73 | + (NSArray *)where:(id)condition inContext:(NSManagedObjectContext *)context;
74 | + (NSArray *)where:(id)condition inContext:(NSManagedObjectContext *)context order:(id)order;
75 | + (NSArray *)where:(id)condition inContext:(NSManagedObjectContext *)context limit:(NSNumber *)limit;
76 | + (NSArray *)where:(id)condition inContext:(NSManagedObjectContext *)context order:(id)order limit:(NSNumber *)limit;
77 | + (instancetype)findOrCreate:(NSDictionary *)properties inContext:(NSManagedObjectContext *)context;
78 | + (instancetype)find:(id)condition inContext:(NSManagedObjectContext *)context;
79 | + (NSUInteger)countInContext:(NSManagedObjectContext *)context;
80 | + (NSUInteger)countWhere:(id)condition inContext:(NSManagedObjectContext *)context;
81 |
82 | #pragma mark - Naming
83 |
84 | + (NSString *)entityName;
85 |
86 | @end
87 |
--------------------------------------------------------------------------------
/Example/SampleProject/SampleProject.xcdatamodeld/SampleProject.xcdatamodel/contents:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/Example/SampleProjectTests/MappingsTests.m:
--------------------------------------------------------------------------------
1 | #import "Kiwi.h"
2 | #import "Person+Mappings.h"
3 | #import "Car+Mappings.h"
4 | #import "InsuranceCompany.h"
5 |
6 | SPEC_BEGIN(MappingsTests)
7 |
8 | describe(@"Mappings", ^{
9 |
10 | NSDictionary *JSON = @{
11 | @"first_name": @"Marin",
12 | @"last_name": @"Usalj",
13 | @"age": @25,
14 | @"is_member": @"true",
15 | @"cars": @[
16 | @{ @"hp": @220, @"make": @"Trabant" },
17 | @{ @"hp": @90, @"make": @"Volkswagen" }
18 | ],
19 | @"manager": @{
20 | @"firstName": @"Delisa",
21 | @"lastName": @"Mason",
22 | @"age": @25,
23 | @"isMember": @NO
24 | },
25 | @"employees": @[
26 | @{ @"first_name": @"Luca" },
27 | @{ @"first_name": @"Tony" },
28 | @{ @"first_name": @"Jim" }
29 | ]
30 | };
31 |
32 | __block Person *person;
33 |
34 | NSManagedObjectContext *newContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
35 | newContext.persistentStoreCoordinator = [[CoreDataManager sharedManager] persistentStoreCoordinator];
36 |
37 | beforeEach(^{
38 | person = [Person create:JSON inContext:newContext];
39 | });
40 |
41 | it(@"caches mappings", ^{
42 | Car *car = [Car createInContext:newContext];
43 | [[Car should] receive:@selector(mappings) withCountAtMost:1];
44 |
45 | [car update:@{ @"hp": @150 }];
46 | [car update:@{ @"make": @"Ford" }];
47 | [car update:@{ @"hp": @150 }];
48 | });
49 |
50 | it(@"uses mapped values when creating", ^{
51 | [[person.firstName should] equal:@"Marin"];
52 | [[person.lastName should] equal:@"Usalj"];
53 | [[person.age should] equal:@25];
54 | });
55 |
56 | it(@"can support snake_case even without mappings", ^{
57 | [[person.isMember should] beTrue];
58 | });
59 |
60 | it(@"supports nested properties", ^{
61 | [[[person should] have:2] cars];
62 | });
63 |
64 | it(@"supports to one relationship", ^{
65 | [[person.manager.firstName should] equal:@"Delisa"];
66 | });
67 |
68 | it(@"supports to many relationship", ^{
69 | [[[person should] have:3] employees];
70 | });
71 |
72 | it(@"uses mappings in findOrCreate", ^{
73 | Person *bob = [Person findOrCreate:@{ @"first_name": @"Bob" }];
74 | [[bob.firstName should] equal:@"Bob"];
75 | });
76 |
77 | it(@"supports creating a parent object using just ID from the server", ^{
78 | Car *car = [Car create:@{ @"hp": @150, @"insurance_id": @1234 }];
79 | [[car.insuranceCompany should] equal:[InsuranceCompany find:@{ @"remoteID": @1234 }]];
80 | });
81 |
82 | it(@"supports creating nested objects directly", ^{
83 | Person *employee = [Person create];
84 | Person *manager = [Person create:@{@"employees": @[employee]}];
85 | [[[manager should] have:1] employees];
86 | });
87 |
88 | it(@"ignores unknown keys", ^{
89 | Car *car = [Car create];
90 | [[car shouldNot] receive:@selector(setPrimitiveValue:forKey:)];
91 | [car update:@{ @"chocolate": @"waffles" }];
92 | });
93 |
94 | it(@"ignores embedded unknown keys", ^{
95 | [[theBlock(^{
96 | Car *car = [Car create];
97 | [car update:@{ @"owner": @{ @"coolness": @(100) } }];
98 | }) shouldNot] raise];
99 | });
100 |
101 | it(@"supports creating nested parent objects using IDs from the server", ^{
102 | Car *car = [Car create:@{ @"insurance_company": @{ @"id" : @1234, @"owner_id" : @4567 }}];
103 | [[car.insuranceCompany.owner should] equal:[Person find:@{ @"remoteID": @4567 }]];
104 | });
105 |
106 | it(@"supports creating full nested parent objects", ^{
107 | Car *car = [Car create:@{ @"insurance_company": @{ @"id" : @1234, @"owner" : @{ @"id" : @4567, @"first_name" : @"Stan" } }}];
108 | [[car.insuranceCompany.owner should] equal:[Person find:@{ @"remoteID": @4567, @"firstName": @"Stan" }]];
109 | });
110 | });
111 |
112 | SPEC_END
113 |
--------------------------------------------------------------------------------
/Classes/NSManagedObject+Mappings.m:
--------------------------------------------------------------------------------
1 | // NSManagedObject+Mappings.m
2 | //
3 | // Copyright (c) 2014 Marin Usalj
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 |
23 | #import "NSManagedObject+Mappings.h"
24 | #import "NSManagedObject+ActiveRecord.h"
25 | #import "ObjectiveSugar.h"
26 |
27 | @implementation NSManagedObject (Mappings)
28 |
29 | + (NSString *)keyForRemoteKey:(NSString *)remoteKey inContext:(NSManagedObjectContext *)context {
30 | if ([self cachedMappings][remoteKey])
31 | return [self cachedMappings][remoteKey][@"key"];
32 |
33 | NSString *camelCasedProperty = [[remoteKey camelCase] stringByReplacingCharactersInRange:NSMakeRange(0, 1)
34 | withString:[[remoteKey substringWithRange:NSMakeRange(0, 1)] lowercaseString]];
35 |
36 | NSEntityDescription *entity = [NSEntityDescription entityForName:[self entityName]
37 | inManagedObjectContext:context];
38 |
39 | if ([entity propertiesByName][camelCasedProperty]) {
40 | [self cacheKey:camelCasedProperty forRemoteKey:camelCasedProperty];
41 | return camelCasedProperty;
42 | }
43 |
44 | [self cacheKey:remoteKey forRemoteKey:remoteKey];
45 | return remoteKey;
46 | }
47 |
48 | + (id)transformValue:(id)value forRemoteKey:(NSString *)remoteKey inContext:(NSManagedObjectContext *)context {
49 | Class class = [self cachedMappings][remoteKey][@"class"];
50 | if (class)
51 | return [self objectOrSetOfObjectsFromValue:value ofClass:class inContext:context];
52 |
53 | return value;
54 | }
55 |
56 | #pragma mark - Private
57 |
58 | + (id)objectOrSetOfObjectsFromValue:(id)value ofClass:class inContext:(NSManagedObjectContext *)context {
59 | if ([value isKindOfClass:class])
60 | return value;
61 |
62 | if ([value isKindOfClass:[NSDictionary class]])
63 | return [class findOrCreate:value inContext:context];
64 |
65 | if ([value isKindOfClass:[NSArray class]])
66 | return [NSSet setWithArray:[value map:^id(id object) {
67 | return [self objectOrSetOfObjectsFromValue:object ofClass:class inContext:context];
68 | }]];
69 |
70 | return [class findOrCreate:@{ [class primaryKey]: value } inContext:context];
71 | }
72 |
73 | + (NSMutableDictionary *)cachedMappings {
74 | NSMutableDictionary *cachedMappings = [self sharedMappings][[self class]];
75 | if (!cachedMappings) {
76 | cachedMappings = [self sharedMappings][(id)[self class]] = [NSMutableDictionary new];
77 |
78 | [[self mappings] each:^(id key, id value) {
79 | if ([value isKindOfClass:[NSString class]])
80 | [self cacheKey:value forRemoteKey:key];
81 |
82 | else {
83 | cachedMappings[key] = value;
84 | [self cacheKey:key forRemoteKey:key];
85 | }
86 | }];
87 | }
88 | return cachedMappings;
89 | }
90 |
91 | + (NSMutableDictionary *)sharedMappings {
92 | static NSMutableDictionary *sharedMappings;
93 | static dispatch_once_t singletonToken;
94 | dispatch_once(&singletonToken, ^{
95 | sharedMappings = [NSMutableDictionary new];
96 | });
97 | return sharedMappings;
98 | }
99 |
100 | + (void)cacheKey:(NSString *)key forRemoteKey:(NSString *)remoteKey {
101 | NSMutableDictionary *mapping = [[self cachedMappings][remoteKey] mutableCopy] ?: [NSMutableDictionary new];
102 | if (mapping[@"key"] == nil) mapping[@"key"] = key;
103 | [self cachedMappings][remoteKey] = mapping;
104 | }
105 |
106 | #pragma mark - Abstract
107 |
108 | + (NSDictionary *)mappings {
109 | return nil;
110 | }
111 |
112 | + (id)primaryKey {
113 | @throw [NSException exceptionWithName:NSStringWithFormat(@"Primary key undefined in %@", self.class)
114 | reason:NSStringWithFormat(@"You need to override %@ +primaryKey if you want to support automatic creation with only object ID",
115 | self.class)
116 | userInfo:nil];
117 | }
118 |
119 | @end
120 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ObjectiveRecord [![CocoaPod][pd-bdg]][pd] [![Build Status][ci-bdg]][ci]
2 |
3 | [pd-bdg]: https://img.shields.io/cocoapods/v/ObjectiveRecord.svg
4 | [pd]: http://cocoadocs.org/docsets/ObjectiveRecord
5 | [ci-bdg]: https://travis-ci.org/supermarin/ObjectiveRecord.svg
6 | [ci]: https://travis-ci.org/supermarin/ObjectiveRecord
7 |
8 | Objective Record is a lightweight ActiveRecord way of managing Core Data
9 | objects. If you've used Ruby on Rails before, it might sound
10 | familiar.
11 |
12 | No AppDelegate code required. It's fully tested with
13 | [Kiwi](https://github.com/allending/Kiwi).
14 |
15 | #### Usage
16 |
17 | 1. Install with [CocoaPods](http://cocoapods.org) or clone
18 | 2. `#import "ObjectiveRecord.h"` in your model or .pch file.
19 |
20 | #### Create / Save / Delete
21 |
22 | ``` objc
23 | Person *john = [Person create];
24 | john.name = @"John";
25 | [john save];
26 | [john delete];
27 |
28 | [Person create:@{
29 | @"name" : @"John",
30 | @"age" : @12,
31 | @"member" : @NO
32 | }];
33 | ```
34 |
35 | #### Finders
36 |
37 | ``` objc
38 | // all Person entities from the database
39 | NSArray *people = [Person all];
40 |
41 | // Person entities with name John
42 | NSArray *johns = [Person where:@"name == 'John'"];
43 |
44 | // And of course, John Doe!
45 | Person *johnDoe = [Person find:@"name == %@ AND surname == %@", @"John", @"Doe"];
46 |
47 | // Members over 18 from NY
48 | NSArray *people = [Person where:@{
49 | @"age" : @18,
50 | @"member" : @YES,
51 | @"state" : @"NY"
52 | }];
53 |
54 | // You can even write your own NSPredicate
55 | NSPredicate *predicate = [NSPredicate
56 | predicateWithFormat:@"(name like[cd] %@) AND (birthday > %@)",
57 | name, birthday];
58 | NSArray *results = [Person where:predicate];
59 | ```
60 |
61 | #### Order and Limit
62 |
63 | ``` objc
64 | // People by their last name ascending
65 | NSArray *sortedPeople = [Person allWithOrder:@"surname"];
66 |
67 | // People named John by their last name Z to A
68 | NSArray *reversedPeople = [Person where:@{@"name" : @"John"}
69 | order:@{@"surname" : @"DESC"}];
70 |
71 | // You can use NSSortDescriptor too
72 | NSArray *people = [Person allWithOrder:[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]];
73 |
74 | // And multiple orderings with any of the above
75 | NSArray *morePeople = [Person allWithOrder:@"surname ASC, name DESC"];
76 |
77 | // Just the first 5 people named John sorted by last name
78 | NSArray *fivePeople = [Person where:@"name == 'John'"
79 | order:@{@"surname" : @"ASC"}
80 | limit:@(5)];
81 | ```
82 |
83 | #### Aggregation
84 |
85 | ``` objc
86 | // count all Person entities
87 | NSUInteger personCount = [Person count];
88 |
89 | // count people named John
90 | NSUInteger johnCount = [Person countWhere:@"name == 'John'"];
91 | ```
92 |
93 | #### Custom ManagedObjectContext
94 |
95 | ``` objc
96 | NSManagedObjectContext *newContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
97 | newContext.persistentStoreCoordinator = [[CoreDataManager instance] persistentStoreCoordinator];
98 |
99 | Person *john = [Person createInContext:newContext];
100 | Person *john = [Person find:@"name == 'John'" inContext:newContext];
101 | NSArray *people = [Person allInContext:newContext];
102 | ```
103 |
104 | #### Custom CoreData model or .sqlite database
105 | If you've added the Core Data manually, you can change the custom model and database name on CoreDataManager
106 | ``` objc
107 | [CoreDataManager sharedManager].modelName = @"MyModelName";
108 | [CoreDataManager sharedManager].databaseName = @"custom_database_name";
109 | ```
110 |
111 | #### Examples
112 |
113 | ``` objc
114 | // find
115 | [[Person all] each:^(Person *person) {
116 | person.member = @NO;
117 | }];
118 |
119 | for(Person *person in [Person all]) {
120 | person.member = @YES;
121 | }
122 |
123 | // create / save
124 | Person *john = [Person create];
125 | john.name = @"John";
126 | john.surname = @"Wayne";
127 | [john save];
128 |
129 | // find / delete
130 | [[Person where: @{ @"member" : @NO }] each:^(Person *person) {
131 | [person delete];
132 | }];
133 | ```
134 | #### Mapping
135 |
136 | The most of the time, your JSON web service returns keys like `first_name`, `last_name`, etc.
137 | Your ObjC implementation has camelCased properties - `firstName`, `lastName`.
138 |
139 | Since v1.2, camel case is supported automatically - you don't have to do anything! Otherwise, if you have more complex mapping, here's how you do it:
140 |
141 | ``` objc
142 | // just override +mappings in your NSManagedObject subclass
143 | // this method is called just once, so you don't have to do any caching / singletons
144 | @implementation Person
145 |
146 | + (NSDictionary *)mappings {
147 | return @{
148 | @"id": @"remoteID",
149 | @"mmbr": @"isMember",
150 | // you can also map relationships, and initialize your graph from a single line
151 | @"employees": @{
152 | @"class": [Person class]
153 | },
154 | @"cars": @{
155 | @"key": @"vehicles",
156 | @"class": [Vehicle class]
157 | }
158 | };
159 | // first_name => firstName is automatically handled
160 | }
161 |
162 | @end
163 | ```
164 |
165 | #### Testing
166 |
167 | ObjectiveRecord supports CoreData's in-memory store. In any place, before your tests start running, it's enough to call
168 | ``` objc
169 | [[CoreDataManager sharedManager] useInMemoryStore];
170 | ```
171 |
172 | #### Roadmap
173 |
174 | - NSIncrementalStore support
175 |
176 | ## License
177 |
178 | ObjectiveRecord is available under the MIT license. See the LICENSE file
179 | for more information.
180 |
--------------------------------------------------------------------------------
/Example/SampleProject.xcodeproj/xcshareddata/xcschemes/SampleProject.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
43 |
49 |
50 |
51 |
57 |
63 |
64 |
65 |
66 |
67 |
72 |
73 |
75 |
81 |
82 |
83 |
84 |
85 |
91 |
92 |
93 |
94 |
103 |
104 |
110 |
111 |
112 |
113 |
114 |
115 |
121 |
122 |
128 |
129 |
130 |
131 |
133 |
134 |
137 |
138 |
139 |
--------------------------------------------------------------------------------
/Classes/CoreDataManager.m:
--------------------------------------------------------------------------------
1 | // CoreDataManager.m
2 | //
3 | // Copyright (c) 2014 Marin Usalj
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 |
23 | #import "CoreDataManager.h"
24 |
25 | @implementation CoreDataManager
26 | @synthesize managedObjectContext = _managedObjectContext;
27 | @synthesize managedObjectModel = _managedObjectModel;
28 | @synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
29 | @synthesize databaseName = _databaseName;
30 | @synthesize modelName = _modelName;
31 |
32 |
33 | + (id)instance {
34 | return [self sharedManager];
35 | }
36 |
37 | + (instancetype)sharedManager {
38 | static CoreDataManager *singleton;
39 | static dispatch_once_t singletonToken;
40 | dispatch_once(&singletonToken, ^{
41 | singleton = [[self alloc] init];
42 | });
43 | return singleton;
44 | }
45 |
46 |
47 | #pragma mark - Private
48 |
49 | - (NSString *)appName {
50 | return [[NSBundle bundleForClass:[self class]] infoDictionary][@"CFBundleName"];
51 | }
52 |
53 | - (NSString *)databaseName {
54 | if (_databaseName != nil) return _databaseName;
55 |
56 | _databaseName = [[[self appName] stringByAppendingString:@".sqlite"] copy];
57 | return _databaseName;
58 | }
59 |
60 | - (NSString *)modelName {
61 | if (_modelName != nil) return _modelName;
62 |
63 | _modelName = [[self appName] copy];
64 | return _modelName;
65 | }
66 |
67 |
68 | #pragma mark - Public
69 |
70 | - (NSManagedObjectContext *)managedObjectContext {
71 | if (_managedObjectContext) return _managedObjectContext;
72 |
73 | if (self.persistentStoreCoordinator) {
74 | _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
75 | [_managedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
76 | }
77 | return _managedObjectContext;
78 | }
79 |
80 | - (NSManagedObjectModel *)managedObjectModel {
81 | if (_managedObjectModel) return _managedObjectModel;
82 |
83 | NSURL *modelURL = [[NSBundle bundleForClass:[self class]] URLForResource:[self modelName] withExtension:@"momd"];
84 | _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
85 | return _managedObjectModel;
86 | }
87 |
88 | - (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
89 | if (_persistentStoreCoordinator) return _persistentStoreCoordinator;
90 |
91 | _persistentStoreCoordinator = [self persistentStoreCoordinatorWithStoreType:NSSQLiteStoreType
92 | storeURL:[self sqliteStoreURL]];
93 | return _persistentStoreCoordinator;
94 | }
95 |
96 | - (void)useInMemoryStore {
97 | _persistentStoreCoordinator = [self persistentStoreCoordinatorWithStoreType:NSInMemoryStoreType storeURL:nil];
98 | }
99 |
100 | - (BOOL)saveContext {
101 | if (self.managedObjectContext == nil) return NO;
102 | if (![self.managedObjectContext hasChanges])return NO;
103 |
104 | NSError *error = nil;
105 |
106 | if (![self.managedObjectContext save:&error]) {
107 | NSLog(@"Unresolved error in saving context! %@, %@", error, [error userInfo]);
108 | return NO;
109 | }
110 |
111 | return YES;
112 | }
113 |
114 |
115 | #pragma mark - SQLite file directory
116 |
117 | - (NSURL *)applicationDocumentsDirectory {
118 | return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
119 | inDomains:NSUserDomainMask] lastObject];
120 | }
121 |
122 | - (NSURL *)applicationSupportDirectory {
123 | return [[[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
124 | inDomains:NSUserDomainMask] lastObject]
125 | URLByAppendingPathComponent:[self appName]];
126 | }
127 |
128 |
129 | #pragma mark - Private
130 |
131 | - (NSPersistentStoreCoordinator *)persistentStoreCoordinatorWithStoreType:(NSString *const)storeType
132 | storeURL:(NSURL *)storeURL {
133 |
134 | NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
135 |
136 | NSDictionary *options = @{ NSMigratePersistentStoresAutomaticallyOption: @YES,
137 | NSInferMappingModelAutomaticallyOption: @YES };
138 |
139 | NSError *error = nil;
140 | if (![coordinator addPersistentStoreWithType:storeType configuration:nil URL:storeURL options:options error:&error])
141 | NSLog(@"ERROR WHILE CREATING PERSISTENT STORE COORDINATOR! %@, %@", error, [error userInfo]);
142 |
143 | return coordinator;
144 | }
145 |
146 | - (NSURL *)sqliteStoreURL {
147 | NSURL *directory = [self isOSX] ? self.applicationSupportDirectory : self.applicationDocumentsDirectory;
148 | NSURL *databaseDir = [directory URLByAppendingPathComponent:[self databaseName]];
149 |
150 | [self createApplicationSupportDirIfNeeded:directory];
151 | return databaseDir;
152 | }
153 |
154 | - (BOOL)isOSX {
155 | if (NSClassFromString(@"UIDevice")) return NO;
156 | return YES;
157 | }
158 |
159 | - (void)createApplicationSupportDirIfNeeded:(NSURL *)url {
160 | if ([[NSFileManager defaultManager] fileExistsAtPath:url.absoluteString]) return;
161 |
162 | [[NSFileManager defaultManager] createDirectoryAtURL:url
163 | withIntermediateDirectories:YES attributes:nil error:nil];
164 | }
165 |
166 | @end
167 |
--------------------------------------------------------------------------------
/Classes/NSManagedObject+ActiveRecord.m:
--------------------------------------------------------------------------------
1 | // NSManagedObject+ActiveRecord.m
2 | //
3 | // Copyright (c) 2014 Marin Usalj
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 |
23 | #import "NSManagedObject+ActiveRecord.h"
24 | #import "ObjectiveSugar.h"
25 |
26 | @implementation NSManagedObjectContext (ActiveRecord)
27 |
28 | + (NSManagedObjectContext *)defaultContext {
29 | return [[CoreDataManager sharedManager] managedObjectContext];
30 | }
31 |
32 | @end
33 |
34 | @implementation NSObject(null)
35 |
36 | - (BOOL)exists {
37 | return self && self != [NSNull null];
38 | }
39 |
40 | @end
41 |
42 | @implementation NSManagedObject (ActiveRecord)
43 |
44 | #pragma mark - Finders
45 |
46 | + (NSArray *)all {
47 | return [self allInContext:[NSManagedObjectContext defaultContext]];
48 | }
49 |
50 | + (NSArray *)allWithOrder:(id)order {
51 | return [self allInContext:[NSManagedObjectContext defaultContext] order:order];
52 | }
53 |
54 | + (NSArray *)allInContext:(NSManagedObjectContext *)context {
55 | return [self allInContext:context order:nil];
56 | }
57 |
58 | + (NSArray *)allInContext:(NSManagedObjectContext *)context order:(id)order {
59 | return [self fetchWithCondition:nil inContext:context withOrder:order fetchLimit:nil];
60 | }
61 |
62 | + (instancetype)findOrCreate:(NSDictionary *)properties {
63 | return [self findOrCreate:properties inContext:[NSManagedObjectContext defaultContext]];
64 | }
65 |
66 | + (instancetype)findOrCreate:(NSDictionary *)properties inContext:(NSManagedObjectContext *)context {
67 | NSDictionary *transformed = [[self class] transformProperties:properties withObject:nil context:context];
68 |
69 | NSManagedObject *existing = [self where:transformed inContext:context].first;
70 | return existing ?: [self create:transformed inContext:context];
71 | }
72 |
73 | + (instancetype)find:(id)condition, ... {
74 | va_list va_arguments;
75 | va_start(va_arguments, condition);
76 | NSPredicate *predicate = [self predicateFromObject:condition arguments:va_arguments];
77 | va_end(va_arguments);
78 |
79 | return [self find:predicate inContext:[NSManagedObjectContext defaultContext]];
80 | }
81 |
82 | + (instancetype)find:(id)condition inContext:(NSManagedObjectContext *)context {
83 | return [self where:condition inContext:context limit:@1].first;
84 | }
85 |
86 | + (NSArray *)where:(id)condition, ... {
87 | va_list va_arguments;
88 | va_start(va_arguments, condition);
89 | NSPredicate *predicate = [self predicateFromObject:condition arguments:va_arguments];
90 | va_end(va_arguments);
91 |
92 | return [self where:predicate inContext:[NSManagedObjectContext defaultContext]];
93 | }
94 |
95 | + (NSArray *)where:(id)condition order:(id)order {
96 | return [self where:condition inContext:[NSManagedObjectContext defaultContext] order:order];
97 | }
98 |
99 | + (NSArray *)where:(id)condition limit:(NSNumber *)limit {
100 | return [self where:condition inContext:[NSManagedObjectContext defaultContext] limit:limit];
101 | }
102 |
103 | + (NSArray *)where:(id)condition order:(id)order limit:(NSNumber *)limit {
104 | return [self where:condition inContext:[NSManagedObjectContext defaultContext] order:order limit:limit];
105 | }
106 |
107 | + (NSArray *)where:(id)condition inContext:(NSManagedObjectContext *)context {
108 | return [self where:condition inContext:context order:nil limit:nil];
109 | }
110 |
111 | + (NSArray *)where:(id)condition inContext:(NSManagedObjectContext *)context order:(id)order {
112 | return [self where:condition inContext:context order:order limit:nil];
113 | }
114 |
115 | + (NSArray *)where:(id)condition inContext:(NSManagedObjectContext *)context limit:(NSNumber *)limit {
116 | return [self where:condition inContext:context order:nil limit:limit];
117 | }
118 |
119 | + (NSArray *)where:(id)condition inContext:(NSManagedObjectContext *)context order:(id)order limit:(NSNumber *)limit {
120 | return [self fetchWithCondition:condition inContext:context withOrder:order fetchLimit:limit];
121 | }
122 |
123 | #pragma mark - Aggregation
124 |
125 | + (NSUInteger)count {
126 | return [self countInContext:[NSManagedObjectContext defaultContext]];
127 | }
128 |
129 | + (NSUInteger)countWhere:(id)condition, ... {
130 | va_list va_arguments;
131 | va_start(va_arguments, condition);
132 | NSPredicate *predicate = [self predicateFromObject:condition arguments:va_arguments];
133 | va_end(va_arguments);
134 |
135 | return [self countWhere:predicate inContext:[NSManagedObjectContext defaultContext]];
136 | }
137 |
138 | + (NSUInteger)countInContext:(NSManagedObjectContext *)context {
139 | return [self countForFetchWithPredicate:nil inContext:context];
140 | }
141 |
142 | + (NSUInteger)countWhere:(id)condition inContext:(NSManagedObjectContext *)context {
143 | NSPredicate *predicate = [self predicateFromObject:condition];
144 |
145 | return [self countForFetchWithPredicate:predicate inContext:context];
146 | }
147 |
148 | #pragma mark - Creation / Deletion
149 |
150 | + (id)create {
151 | return [self createInContext:[NSManagedObjectContext defaultContext]];
152 | }
153 |
154 | + (id)create:(NSDictionary *)attributes {
155 | return [self create:attributes inContext:[NSManagedObjectContext defaultContext]];
156 | }
157 |
158 | + (id)create:(NSDictionary *)attributes inContext:(NSManagedObjectContext *)context {
159 | unless([attributes exists]) return nil;
160 |
161 | NSManagedObject *newEntity = [self createInContext:context];
162 | [newEntity update:attributes];
163 |
164 | return newEntity;
165 | }
166 |
167 | + (id)createInContext:(NSManagedObjectContext *)context {
168 | return [NSEntityDescription insertNewObjectForEntityForName:[self entityName]
169 | inManagedObjectContext:context];
170 | }
171 |
172 | - (void)update:(NSDictionary *)attributes {
173 | unless([attributes exists]) return;
174 |
175 | NSDictionary *transformed = [[self class] transformProperties:attributes withObject:self context:self.managedObjectContext];
176 |
177 | for (NSString *key in transformed) [self willChangeValueForKey:key];
178 | [transformed each:^(NSString *key, id value) {
179 | [self setSafeValue:value forKey:key];
180 | }];
181 | for (NSString *key in transformed) [self didChangeValueForKey:key];
182 | }
183 |
184 | - (BOOL)save {
185 | return [self saveTheContext];
186 | }
187 |
188 | - (void)delete {
189 | [self.managedObjectContext deleteObject:self];
190 | }
191 |
192 | + (void)deleteAll {
193 | [self deleteAllInContext:[NSManagedObjectContext defaultContext]];
194 | }
195 |
196 | + (void)deleteAllInContext:(NSManagedObjectContext *)context {
197 | [[self allInContext:context] each:^(id object) {
198 | [object delete];
199 | }];
200 | }
201 |
202 | #pragma mark - Naming
203 |
204 | + (NSString *)entityName {
205 | return NSStringFromClass(self);
206 | }
207 |
208 | #pragma mark - Private
209 |
210 | + (NSDictionary *)transformProperties:(NSDictionary *)properties withObject:(NSManagedObject *)object context:(NSManagedObjectContext *)context {
211 | NSEntityDescription *entity = [NSEntityDescription entityForName:[self entityName] inManagedObjectContext:context];
212 |
213 | NSDictionary *attributes = [entity attributesByName];
214 | NSDictionary *relationships = [entity relationshipsByName];
215 |
216 | NSMutableDictionary *transformed = [NSMutableDictionary dictionaryWithCapacity:[properties count]];
217 |
218 | for (NSString *key in properties) {
219 | NSString *localKey = [self keyForRemoteKey:key inContext:context];
220 | if (attributes[localKey] || relationships[localKey]) {
221 | id value = [[self class] transformValue:properties[key] forRemoteKey:key inContext:context];
222 | if (object) {
223 | id localValue = [object primitiveValueForKey:localKey];
224 | if ([localValue isEqual:value] || (localValue == nil && value == [NSNull null]))
225 | continue;
226 | }
227 | transformed[localKey] = value;
228 | } else {
229 | #if DEBUG
230 | NSLog(@"Discarding key ('%@') from properties on class ('%@'): no attribute or relationship found",
231 | key, [self class]);
232 | #endif
233 | }
234 | }
235 |
236 | return transformed;
237 | }
238 |
239 | + (NSPredicate *)predicateFromDictionary:(NSDictionary *)dict {
240 | NSArray *subpredicates = [dict map:^(NSString *key, id value) {
241 | return [NSPredicate predicateWithFormat:@"%K = %@", key, value];
242 | }];
243 |
244 | return [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];
245 | }
246 |
247 | + (NSPredicate *)predicateFromObject:(id)condition {
248 | return [self predicateFromObject:condition arguments:NULL];
249 | }
250 |
251 | + (NSPredicate *)predicateFromObject:(id)condition arguments:(va_list)arguments {
252 | if ([condition isKindOfClass:[NSPredicate class]])
253 | return condition;
254 |
255 | if ([condition isKindOfClass:[NSString class]])
256 | return [NSPredicate predicateWithFormat:condition arguments:arguments];
257 |
258 | if ([condition isKindOfClass:[NSDictionary class]])
259 | return [self predicateFromDictionary:condition];
260 |
261 | return nil;
262 | }
263 |
264 | + (NSSortDescriptor *)sortDescriptorFromDictionary:(NSDictionary *)dict {
265 | BOOL isAscending = ![[dict.allValues.first uppercaseString] isEqualToString:@"DESC"];
266 | return [NSSortDescriptor sortDescriptorWithKey:dict.allKeys.first
267 | ascending:isAscending];
268 | }
269 |
270 | + (NSSortDescriptor *)sortDescriptorFromString:(NSString *)order {
271 | NSArray *components = [order split];
272 |
273 | NSString *key = [components firstObject];
274 | NSString *value = [components count] > 1 ? components[1] : @"ASC";
275 |
276 | return [self sortDescriptorFromDictionary:@{key: value}];
277 |
278 | }
279 |
280 | + (NSSortDescriptor *)sortDescriptorFromObject:(id)order {
281 | if ([order isKindOfClass:[NSSortDescriptor class]])
282 | return order;
283 |
284 | if ([order isKindOfClass:[NSString class]])
285 | return [self sortDescriptorFromString:order];
286 |
287 | if ([order isKindOfClass:[NSDictionary class]])
288 | return [self sortDescriptorFromDictionary:order];
289 |
290 | return nil;
291 | }
292 |
293 | + (NSArray *)sortDescriptorsFromObject:(id)order {
294 | if ([order isKindOfClass:[NSString class]])
295 | order = [order componentsSeparatedByString:@","];
296 |
297 | if ([order isKindOfClass:[NSArray class]])
298 | return [order map:^id (id object) {
299 | return [self sortDescriptorFromObject:object];
300 | }];
301 |
302 | return @[[self sortDescriptorFromObject:order]];
303 | }
304 |
305 | + (NSFetchRequest *)createFetchRequestInContext:(NSManagedObjectContext *)context {
306 | NSFetchRequest *request = [NSFetchRequest new];
307 | NSEntityDescription *entity = [NSEntityDescription entityForName:[self entityName]
308 | inManagedObjectContext:context];
309 | [request setEntity:entity];
310 | return request;
311 | }
312 |
313 | + (NSArray *)fetchWithCondition:(id)condition
314 | inContext:(NSManagedObjectContext *)context
315 | withOrder:(id)order
316 | fetchLimit:(NSNumber *)fetchLimit {
317 |
318 | NSFetchRequest *request = [self createFetchRequestInContext:context];
319 |
320 | if (condition)
321 | [request setPredicate:[self predicateFromObject:condition]];
322 |
323 | if (order)
324 | [request setSortDescriptors:[self sortDescriptorsFromObject:order]];
325 |
326 | if (fetchLimit)
327 | [request setFetchLimit:[fetchLimit integerValue]];
328 |
329 | return [context executeFetchRequest:request error:nil];
330 | }
331 |
332 | + (NSUInteger)countForFetchWithPredicate:(NSPredicate *)predicate
333 | inContext:(NSManagedObjectContext *)context {
334 | NSFetchRequest *request = [self createFetchRequestInContext:context];
335 | [request setPredicate:predicate];
336 |
337 | return [context countForFetchRequest:request error:nil];
338 | }
339 |
340 | - (BOOL)saveTheContext {
341 | if (self.managedObjectContext == nil ||
342 | ![self.managedObjectContext hasChanges]) return YES;
343 |
344 | NSError *error = nil;
345 | BOOL save = [self.managedObjectContext save:&error];
346 |
347 | if (!save || error) {
348 | NSLog(@"Unresolved error in saving context for entity:\n%@!\nError: %@", self, error);
349 | return NO;
350 | }
351 |
352 | return YES;
353 | }
354 |
355 | - (void)setSafeValue:(id)value forKey:(NSString *)key {
356 | if (value == nil || value == [NSNull null]) {
357 | [self setNilValueForKey:key];
358 | return;
359 | }
360 |
361 | NSAttributeDescription *attribute = [[self entity] attributesByName][key];
362 | NSAttributeType attributeType = [attribute attributeType];
363 |
364 | if ((attributeType == NSStringAttributeType) && ([value isKindOfClass:[NSNumber class]]))
365 | value = [value stringValue];
366 |
367 | else if ([value isKindOfClass:[NSString class]]) {
368 |
369 | if ([self isIntegerAttributeType:attributeType])
370 | value = [NSNumber numberWithLongLong:[value longLongValue]];
371 |
372 | else if (attributeType == NSBooleanAttributeType)
373 | value = [NSNumber numberWithBool:[value boolValue]];
374 |
375 | else if ([self isFloatAttributeType:attributeType])
376 | value = [NSNumber numberWithDouble:[value doubleValue]];
377 |
378 | else if (attributeType == NSDateAttributeType)
379 | value = [self.defaultFormatter dateFromString:value];
380 | }
381 |
382 | [self setPrimitiveValue:value forKey:key];
383 | }
384 |
385 | - (BOOL)isIntegerAttributeType:(NSAttributeType)attributeType {
386 | return (attributeType == NSInteger16AttributeType) ||
387 | (attributeType == NSInteger32AttributeType) ||
388 | (attributeType == NSInteger64AttributeType);
389 | }
390 |
391 | - (BOOL)isFloatAttributeType:(NSAttributeType)attributeType {
392 | return (attributeType == NSFloatAttributeType) ||
393 | (attributeType == NSDoubleAttributeType);
394 | }
395 |
396 | #pragma mark - Date Formatting
397 |
398 | - (NSDateFormatter *)defaultFormatter {
399 | static NSDateFormatter *sharedFormatter;
400 | static dispatch_once_t singletonToken;
401 | dispatch_once(&singletonToken, ^{
402 | sharedFormatter = [[NSDateFormatter alloc] init];
403 | [sharedFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss z"];
404 | });
405 |
406 | return sharedFormatter;
407 | }
408 |
409 | @end
410 |
--------------------------------------------------------------------------------
/Example/SampleProjectTests/FindersAndCreatorsTests.m:
--------------------------------------------------------------------------------
1 | #import "Kiwi.h"
2 | #import "ObjectiveSugar.h"
3 | #import "Person+Mappings.h"
4 | #import "OBRPerson.h"
5 | #import "Car+Mappings.h"
6 |
7 | static NSString *UNIQUE_NAME = @"ldkhbfaewlfbaewljfhb";
8 | static NSString *UNIQUE_SURNAME = @"laewfbaweljfbawlieufbawef";
9 |
10 |
11 | #pragma mark - Helpers
12 |
13 | Person *fetchUniquePerson() {
14 | Person *person = [Person where:[NSString stringWithFormat:@"firstName = '%@' AND lastName = '%@'",
15 | UNIQUE_NAME, UNIQUE_SURNAME]].first;
16 | return person;
17 | }
18 |
19 | NSManagedObjectContext *createNewContext() {
20 | NSManagedObjectContext *newContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
21 | newContext.persistentStoreCoordinator = [[CoreDataManager sharedManager] persistentStoreCoordinator];
22 | return newContext;
23 | }
24 |
25 | void createSomePeople(NSArray *names, NSArray *surnames, NSManagedObjectContext *context) {
26 | for (int i = 0; i < names.count; i++) {
27 | Person *person = [Person createInContext:context];
28 | person.firstName = names[i];
29 | person.lastName = surnames[i];
30 | person.age = @(i);
31 | person.isMember = @YES;
32 | person.anniversary = [NSDate dateWithTimeIntervalSince1970:0];
33 | [person save];
34 | }
35 | }
36 |
37 |
38 | SPEC_BEGIN(FindersAndCreators)
39 |
40 | describe(@"Find / Create / Save / Delete specs", ^{
41 |
42 | NSArray *names = @[@"John", @"Steve", @"Neo", UNIQUE_NAME];
43 | NSArray *surnames = @[@"Doe", @"Jobs", @"Anderson", UNIQUE_SURNAME];
44 |
45 | beforeEach(^{
46 | [Person deleteAll];
47 | createSomePeople(names, surnames, NSManagedObjectContext.defaultContext);
48 | });
49 |
50 | context(@"Finders", ^{
51 |
52 | it(@"Finds ALL the entities!", ^{
53 | [[[Person all] should] haveCountOf:[names count]];
54 | });
55 |
56 | it(@"Finds using [Entity where: STRING]", ^{
57 |
58 | Person *unique = [Person where:[NSPredicate predicateWithFormat:@"firstName == %@",UNIQUE_NAME]].first;
59 | [[unique.lastName should] equal:UNIQUE_SURNAME];
60 |
61 | });
62 |
63 | it(@"Finds using [Entity where: STRING and ARGUMENTS]", ^{
64 |
65 | Person *unique = [Person where:@"firstName == %@", UNIQUE_NAME].first;
66 | [[unique.lastName should] equal:UNIQUE_SURNAME];
67 |
68 | });
69 |
70 | it(@"Finds using [Entity where: DICTIONARY]", ^{
71 | Person *person = [Person where:@{
72 | @"firstName": @"John",
73 | @"lastName": @"Doe",
74 | @"age": @0,
75 | @"isMember": @1,
76 | @"anniversary": [NSDate dateWithTimeIntervalSince1970:0]
77 | }].first;
78 |
79 | [[person.firstName should] equal:@"John"];
80 | [[person.lastName should] equal:@"Doe"];
81 | [[person.age should] equal:@0];
82 | [[person.isMember should] equal:@(YES)];
83 | [[person.anniversary should] equal:[NSDate dateWithTimeIntervalSince1970:0]];
84 | });
85 |
86 | it(@"Finds and creates if there was no object", ^{
87 | [Person deleteAll];
88 | Person *luis = [Person findOrCreate:@{ @"firstName": @"Luis" }];
89 | [[luis.firstName should] equal:@"Luis"];
90 | });
91 |
92 | it(@"doesn't create duplicate objects on findOrCreate", ^{
93 | [Person deleteAll];
94 | [@4 times:^{
95 | [Person findOrCreate:@{ @"firstName": @"Luis" }];
96 | }];
97 | [[[Person all] should] haveCountOf:1];
98 | });
99 |
100 | it(@"Finds the first match", ^{
101 | Person *johnDoe = [Person find:@{ @"firstName": @"John",
102 | @"lastName": @"Doe" }];
103 | [[johnDoe.firstName should] equal:@"John"];
104 | });
105 |
106 | it(@"Finds the first match using [Entity find: STRING]", ^{
107 | Person *johnDoe = [Person find:@"firstName = 'John' AND lastName = 'Doe'"];
108 | [[johnDoe.firstName should] equal:@"John"];
109 | });
110 |
111 | it(@"Finds the first match using [Entity find: STRING and ARGUMENTS]", ^{
112 | Person *johnDoe = [Person find:@"firstName = %@ AND lastName = %@", @"John", @"Doe"];
113 | [[johnDoe.firstName should] equal:@"John"];
114 | });
115 |
116 | it(@"doesn't create an object on find", ^{
117 | Person *cat = [Person find:@{ @"firstName": @"Cat" }];
118 | [cat shouldBeNil];
119 | });
120 |
121 | it(@"Finds a limited number of results", ^{
122 | [@4 times:^{
123 | Person *newPerson = [Person create];
124 | newPerson.firstName = @"John";
125 | [newPerson save];
126 | }];
127 | [[[Person where:@{ @"firstName": @"John" } limit:@2] should] haveCountOf:2];
128 | });
129 | });
130 |
131 | context(@"Ordering", ^{
132 |
133 | id (^firstNameMapper)(Person *) = ^id (Person *p) { return p.firstName; };
134 | id (^lastNameMapper)(Person *) = ^id (Person *p) { return p.lastName; };
135 |
136 | beforeEach(^{
137 | [Person deleteAll];
138 | createSomePeople(@[@"Abe", @"Bob", @"Cal", @"Don"],
139 | @[@"Zed", @"Mol", @"Gaz", @"Mol"],
140 | [NSManagedObjectContext defaultContext]);
141 | });
142 |
143 | it(@"orders results by a single string property", ^{
144 | NSArray *resultLastNames = [[Person allWithOrder:@"lastName"]
145 | map:lastNameMapper];
146 | [[resultLastNames should] equal:@[@"Gaz", @"Mol", @"Mol", @"Zed"]];
147 | });
148 |
149 | it(@"orders results by a single string property descending", ^{
150 | NSArray *resultFirstNames = [[Person allWithOrder:@"firstName DESC"]
151 | map:firstNameMapper];
152 | [[resultFirstNames should] equal:@[@"Don", @"Cal", @"Bob", @"Abe"]];
153 | });
154 |
155 | it(@"orders results by multiple string properties descending", ^{
156 | NSArray *resultFirstNames = [[Person allWithOrder:@"lastName, firstName DESC"]
157 | map:firstNameMapper];
158 | [[resultFirstNames should] equal:@[@"Cal", @"Don", @"Bob", @"Abe"]];
159 | });
160 |
161 | it(@"orders results by multiple properties", ^{
162 | NSArray *resultFirstNames = [[Person allWithOrder:@[@"lastName", @"firstName"]]
163 | map:firstNameMapper];
164 | [[resultFirstNames should] equal:@[@"Cal", @"Bob", @"Don", @"Abe"]];
165 | });
166 |
167 | it(@"orders results by property ascending", ^{
168 | NSArray *resultFirstNames = [[Person allWithOrder:@{@"firstName" : @"ASC"}]
169 | map:firstNameMapper];
170 | [[resultFirstNames should] equal:@[@"Abe", @"Bob", @"Cal", @"Don"]];
171 | });
172 |
173 | it(@"orders results by property descending", ^{
174 | NSArray *resultFirstNames = [[Person allWithOrder:@[@{@"firstName" : @"DESC"}]]
175 | map:firstNameMapper];
176 | [[resultFirstNames should] equal:@[@"Don", @"Cal", @"Bob", @"Abe"]];
177 | });
178 |
179 | it(@"orders results by sort descriptors", ^{
180 | NSArray *resultFirstNames = [[Person allWithOrder:@[[NSSortDescriptor sortDescriptorWithKey:@"lastName" ascending:YES],
181 | [NSSortDescriptor sortDescriptorWithKey:@"firstName" ascending:NO]]]
182 | map:firstNameMapper];
183 | [[resultFirstNames should] equal:@[@"Cal", @"Don", @"Bob", @"Abe"]];
184 | });
185 |
186 | it(@"orders found results", ^{
187 | NSArray *resultFirstNames = [[Person where:@{@"lastName" : @"Mol"} order:@"firstName"]
188 | map:firstNameMapper];
189 | [[resultFirstNames should] equal:@[@"Bob", @"Don"]];
190 | });
191 |
192 | it(@"orders limited results", ^{
193 | NSArray *resultLastNames = [[Person where:nil order:@"lastName" limit:@(2)]
194 | map:lastNameMapper];
195 | [[resultLastNames should] equal:@[@"Gaz", @"Mol"]];
196 | });
197 |
198 | it(@"orders found and limited results", ^{
199 | NSArray *resultFirstNames = [[Person where:@{@"lastName" : @"Mol"}
200 | inContext:[NSManagedObjectContext defaultContext]
201 | order:@[@{@"lastName" : @"ASC"},
202 | @{@"firstName" : @"DESC"}]
203 | limit:@(1)]
204 | map:firstNameMapper];
205 | [[resultFirstNames should] equal:@[@"Don"]];
206 | });
207 | });
208 |
209 | context(@"Counting", ^{
210 |
211 | it(@"counts all entities", ^{
212 | [[@([Person count]) should] equal:@(4)];
213 | });
214 |
215 | it(@"counts found entities", ^{
216 | NSUInteger count = [Person countWhere:@{@"firstName" : @"Neo"}];
217 | [[@(count) should] equal:@(1)];
218 | });
219 |
220 | it(@"counts zero when none found", ^{
221 | NSUInteger count = [Person countWhere:@{@"firstName" : @"Nobody"}];
222 | [[@(count) should] equal:@(0)];
223 | });
224 |
225 | it(@"counts with variable arguments", ^{
226 | NSUInteger count = [Person countWhere:@"firstName = %@", @"Neo"];
227 | [[@(count) should] equal:@(1)];
228 | });
229 | });
230 |
231 | context(@"Creating", ^{
232 |
233 | it(@"creates without arguments", ^{
234 | Person *person = [Person create];
235 | person.firstName = @"marin";
236 | person.lastName = UNIQUE_SURNAME;
237 | [[[[Person where:@"firstName == 'marin'"].first lastName] should] equal:UNIQUE_SURNAME];
238 | });
239 |
240 |
241 | it(@"creates with dict", ^{
242 | Person *person = [Person create:@{
243 | @"firstName": @"Marin",
244 | @"lastName": @"Usalj",
245 | @"age": @25
246 | }];
247 | [[person.firstName should] equal:@"Marin"];
248 | [[person.lastName should] equal:@"Usalj"];
249 | [[person.age should] equal:theValue(25)];
250 | });
251 |
252 | it(@"Doesn't create with nulls", ^{
253 | [[Person create:nil] shouldBeNil];
254 | [[Person create:(id)[NSNull null]] shouldBeNil];
255 | });
256 |
257 | });
258 |
259 | context(@"Updating", ^{
260 |
261 | it(@"Can update using dictionary", ^{
262 | Person *person = [Person create];
263 | [person update:@{ @"firstName": @"Jonathan", @"age": @50 }];
264 |
265 | [[person.firstName should] equal:@"Jonathan"];
266 | [[person.age should] equal:@50];
267 | });
268 |
269 | it(@"sets [NSNull null] properties as nil", ^{
270 | Person *person = [Person create];
271 | [person update:@{ @"is_member": @YES }];
272 | [person update:@{ @"is_member": [NSNull null] }];
273 | [person.isMember shouldBeNil];
274 | });
275 |
276 | it(@"stringifies numbers", ^{
277 | Person *person = [Person create:@{ @"first_name": @123 }];
278 | [[person.firstName should] equal:@"123"];
279 | });
280 |
281 | it(@"converts strings to integers", ^{
282 | Person *person = [Person create:@{ @"age": @"25" }];
283 | [[person.age should] equal:@25];
284 | });
285 |
286 | it(@"converts strings to floats", ^{
287 | Person *person = [Person create:@{ @"savings": @"1500.12" }];
288 | [[person.savings should] equal:@(1500.12f)];
289 | });
290 |
291 | it(@"converts strings to dates", ^{
292 | NSDateFormatter *formatta = [NSDateFormatter new];
293 | [formatta setDateFormat:@"yyyy-MM-dd HH:mm:ss z"];
294 |
295 | NSDate *date = [NSDate date];
296 | Person *person = [Person create:@{ @"anniversary": [formatta stringFromDate:date] }];
297 | [[@([date timeIntervalSinceDate:person.anniversary]) should] beLessThan:@1];
298 | });
299 |
300 | it(@"doesn't update with nulls", ^{
301 | Person *person = fetchUniquePerson();
302 | [person update:nil];
303 | [person update:(id)[NSNull null]];
304 |
305 | [[person.firstName should] equal:UNIQUE_NAME];
306 | });
307 |
308 | it(@"doesn't always create new relationship object", ^{
309 | Car *car = [Car create:@{ @"hp": @150, @"owner": @{ @"firstName": @"asetnset" } }];
310 | [@3 times:^{
311 | [car update:@{ @"make": @"Porsche", @"owner": @{ @"firstName": @"asetnset" } }];
312 | }];
313 | [[[Person where:@{ @"firstName": @"asetnset" }] should] haveCountOf:1];
314 | });
315 |
316 | it(@"doesn't mark records as having changes when values are the same", ^{
317 | Person *person = fetchUniquePerson();
318 | [person update:@{@"firstName": person.firstName}];
319 | [[@([person hasChanges]) should] beNo];
320 | });
321 | });
322 |
323 | context(@"Saving", ^{
324 |
325 | __block Person *person;
326 |
327 | beforeEach(^{
328 | person = fetchUniquePerson();
329 | person.firstName = @"changed attribute for save";
330 | });
331 |
332 | afterEach(^{
333 | person.firstName = UNIQUE_NAME;
334 | [person save];
335 | });
336 |
337 | it(@"uses the object's context", ^{
338 | [[person.managedObjectContext should] receive:@selector(save:) andReturn:theValue(YES)];
339 | [person save];
340 | });
341 |
342 | it(@"returns YES if save has succeeded", ^{
343 | [[@([person save]) should] beTrue];
344 | [[@([person save]) should] beTrue];
345 | });
346 |
347 | it(@"returns NO if save hasn't succeeded", ^{
348 | [[person.managedObjectContext should] receive:@selector(save:) andReturn:theValue(NO)];
349 | [[@([person save]) should] beFalse];
350 | });
351 |
352 | });
353 |
354 |
355 | context(@"Deleting", ^{
356 |
357 | it(@"Deletes the object from database with -delete", ^{
358 | Person *person = fetchUniquePerson();
359 | [person shouldNotBeNil];
360 | [person delete];
361 | [fetchUniquePerson() shouldBeNil];
362 | });
363 |
364 | it(@"Deletes everything from database with +deleteAll", ^{
365 | [Person deleteAll];
366 | [[[Person all] should] beEmpty];
367 | });
368 |
369 | });
370 |
371 |
372 | context(@"All from above, in a separate context!", ^{
373 |
374 | __block NSManagedObjectContext *newContext;
375 |
376 | beforeEach(^{
377 | [Person deleteAll];
378 | [NSManagedObjectContext.defaultContext save:nil];
379 |
380 | newContext = createNewContext();
381 |
382 | [newContext performBlockAndWait:^{
383 | Person *newPerson = [Person createInContext:newContext];
384 | newPerson.firstName = @"Joshua";
385 | newPerson.lastName = @"Jobs";
386 | newPerson.age = [NSNumber numberWithInt:100];
387 | [newPerson save];
388 | }];
389 | });
390 |
391 | it(@"Creates in a separate context", ^{
392 | [[NSEntityDescription should] receive:@selector(insertNewObjectForEntityForName:inManagedObjectContext:)
393 | andReturn:nil
394 | withArguments:@"Person", newContext];
395 |
396 | [Person createInContext:newContext];
397 | });
398 |
399 | it(@"Creates with dictionary in a separate context", ^{
400 | [[NSEntityDescription should] receive:@selector(insertNewObjectForEntityForName:inManagedObjectContext:)
401 | withArguments:@"Person", newContext];
402 |
403 | [Person create:[NSDictionary dictionary] inContext:newContext];
404 | });
405 |
406 | it(@"Finds in a separate context", ^{
407 | [newContext performBlockAndWait:^{
408 | Person *found = [Person where:@{ @"firstName": @"Joshua" } inContext:newContext].first;
409 | [[found.lastName should] equal:@"Jobs"];
410 | }];
411 | });
412 |
413 | it(@"Finds all in a separate context", ^{
414 | __block NSManagedObjectContext *anotherContext = createNewContext();
415 | __block NSArray *newPeople;
416 |
417 | [anotherContext performBlockAndWait:^{
418 | [Person deleteAll];
419 | [NSManagedObjectContext.defaultContext save:nil];
420 |
421 | createSomePeople(names, surnames, anotherContext);
422 | newPeople = [Person allInContext:anotherContext];
423 | }];
424 |
425 | [[newPeople should] haveCountOf:names.count];
426 | });
427 |
428 | it(@"Finds the first match in a separate context", ^{
429 | NSDictionary *attributes = @{ @"firstName": @"Joshua",
430 | @"lastName": @"Jobs" };
431 | Person *joshua = [Person find:attributes inContext:newContext];
432 | [[joshua.firstName should] equal:@"Joshua"];
433 | });
434 |
435 | it(@"Finds a limited number of results in a separate context", ^{
436 | [@4 times:^{
437 | [newContext performBlockAndWait:^{
438 | Person *newPerson = [Person createInContext:newContext];
439 | newPerson.firstName = @"Joshua";
440 | [newPerson save];
441 | }];
442 | }];
443 | NSArray *people = [Person where:@{ @"firstName": @"Joshua"}
444 | inContext:newContext
445 | limit:@2];
446 | [[people should] haveCountOf:2];
447 | });
448 |
449 |
450 | it(@"Find or create in a separate context", ^{
451 | [newContext performBlockAndWait:^{
452 | Person *luis = [Person findOrCreate:@{ @"firstName": @"Luis" } inContext:newContext];
453 | [[luis.firstName should] equal:@"Luis"];
454 | }];
455 | });
456 |
457 | it(@"Deletes all from context", ^{
458 | [newContext performBlockAndWait:^{
459 | [Person deleteAllInContext:newContext];
460 | [[[Person allInContext:newContext] should] beEmpty];
461 | }];
462 | });
463 |
464 | });
465 |
466 |
467 | context(@"With a different class name to the entity name", ^{
468 |
469 | NSManagedObjectContext *newContext = createNewContext();
470 |
471 | it(@"Has the correct entity name", ^{
472 | [[[OBRPerson entityName] should] equal:@"OtherPerson"];
473 | });
474 |
475 | it(@"Fetches the correct entity", ^{
476 | [[NSEntityDescription should] receive:@selector(entityForName:inManagedObjectContext:)
477 | andReturn:[NSEntityDescription entityForName:@"OtherPerson" inManagedObjectContext:newContext]
478 | withArguments:@"OtherPerson", newContext];
479 |
480 | [OBRPerson allInContext:newContext];
481 | });
482 |
483 | it(@"Creates the correct entity", ^{
484 | [[NSEntityDescription should] receive:@selector(insertNewObjectForEntityForName:inManagedObjectContext:)
485 | withArguments:@"OtherPerson", newContext];
486 |
487 | [OBRPerson createInContext:newContext];
488 | });
489 |
490 | });
491 |
492 |
493 | });
494 |
495 | SPEC_END
496 |
497 |
--------------------------------------------------------------------------------
/Example/SampleProject.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 496F953D174CCCBA00220FD1 /* OBRPerson.m in Sources */ = {isa = PBXBuildFile; fileRef = 496F953C174CCCBA00220FD1 /* OBRPerson.m */; };
11 | 6433E5B9184D89EC008EF278 /* Person.m in Sources */ = {isa = PBXBuildFile; fileRef = 6433E5B8184D89EC008EF278 /* Person.m */; };
12 | 6433E5C2184E83C2008EF278 /* InsuranceCompany.m in Sources */ = {isa = PBXBuildFile; fileRef = 6433E5C1184E83C2008EF278 /* InsuranceCompany.m */; };
13 | 6433E5C5184E83D6008EF278 /* Car.m in Sources */ = {isa = PBXBuildFile; fileRef = 6433E5C4184E83D6008EF278 /* Car.m */; };
14 | 892C1E91165D54160077F2CB /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 892C1E90165D54160077F2CB /* Default-568h@2x.png */; };
15 | 8956CE641754EE6A00BD551C /* MappingsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8956CE631754EE6A00BD551C /* MappingsTests.m */; };
16 | 89702F25173E57AA00149BD5 /* SampleProject.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 89F1EEBB15A4C73B00AE4FB4 /* SampleProject.xcdatamodeld */; };
17 | 897AD0C71758D99A006869BA /* Person+Mappings.m in Sources */ = {isa = PBXBuildFile; fileRef = 897AD0C61758D99A006869BA /* Person+Mappings.m */; };
18 | 897AD0CA1758D9A6006869BA /* Car+Mappings.m in Sources */ = {isa = PBXBuildFile; fileRef = 897AD0C91758D9A6006869BA /* Car+Mappings.m */; };
19 | 897D09FE15B0945400722A8F /* FindersAndCreatorsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 897D09FD15B0945400722A8F /* FindersAndCreatorsTests.m */; };
20 | 8990C3161785C2F500212182 /* CoreDataManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8990C3151785C2F500212182 /* CoreDataManagerTests.m */; };
21 | 89F1EEA815A4C73B00AE4FB4 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 89F1EEA715A4C73B00AE4FB4 /* UIKit.framework */; };
22 | 89F1EEAA15A4C73B00AE4FB4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 89F1EEA915A4C73B00AE4FB4 /* Foundation.framework */; };
23 | 89F1EEAC15A4C73B00AE4FB4 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 89F1EEAB15A4C73B00AE4FB4 /* CoreGraphics.framework */; };
24 | 89F1EEAE15A4C73B00AE4FB4 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 89F1EEAD15A4C73B00AE4FB4 /* CoreData.framework */; };
25 | 89F1EEB415A4C73B00AE4FB4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 89F1EEB215A4C73B00AE4FB4 /* InfoPlist.strings */; };
26 | 89F1EEB615A4C73B00AE4FB4 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 89F1EEB515A4C73B00AE4FB4 /* main.m */; };
27 | 89F1EEBA15A4C73B00AE4FB4 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 89F1EEB915A4C73B00AE4FB4 /* AppDelegate.m */; };
28 | 89F1EEBD15A4C73B00AE4FB4 /* SampleProject.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 89F1EEBB15A4C73B00AE4FB4 /* SampleProject.xcdatamodeld */; };
29 | 89F1EEC615A4C73B00AE4FB4 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 89F1EEA715A4C73B00AE4FB4 /* UIKit.framework */; };
30 | 89F1EEC715A4C73B00AE4FB4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 89F1EEA915A4C73B00AE4FB4 /* Foundation.framework */; };
31 | 89F1EEC815A4C73B00AE4FB4 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 89F1EEAD15A4C73B00AE4FB4 /* CoreData.framework */; };
32 | 89F1EED015A4C73B00AE4FB4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 89F1EECE15A4C73B00AE4FB4 /* InfoPlist.strings */; };
33 | 9701BF4FCD84477FA76795B9 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0EE71E2B92554B48A0914E08 /* libPods.a */; };
34 | EBC606FFB7794B46B70E85E5 /* libPods-SampleProjectTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C3D6081CC9E14BB688C96629 /* libPods-SampleProjectTests.a */; };
35 | FA91A5E91875B9FE00BE74DD /* InsuranceCompany+Mappings.m in Sources */ = {isa = PBXBuildFile; fileRef = FA91A5E81875B9FD00BE74DD /* InsuranceCompany+Mappings.m */; };
36 | /* End PBXBuildFile section */
37 |
38 | /* Begin PBXContainerItemProxy section */
39 | 89F1EEC915A4C73B00AE4FB4 /* PBXContainerItemProxy */ = {
40 | isa = PBXContainerItemProxy;
41 | containerPortal = 89F1EE9A15A4C73B00AE4FB4 /* Project object */;
42 | proxyType = 1;
43 | remoteGlobalIDString = 89F1EEA215A4C73B00AE4FB4;
44 | remoteInfo = SampleProject;
45 | };
46 | /* End PBXContainerItemProxy section */
47 |
48 | /* Begin PBXFileReference section */
49 | 0EE71E2B92554B48A0914E08 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; };
50 | 496F953B174CCCBA00220FD1 /* OBRPerson.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OBRPerson.h; sourceTree = ""; };
51 | 496F953C174CCCBA00220FD1 /* OBRPerson.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OBRPerson.m; sourceTree = ""; };
52 | 6433E5B7184D89EC008EF278 /* Person.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Person.h; sourceTree = ""; };
53 | 6433E5B8184D89EC008EF278 /* Person.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Person.m; sourceTree = ""; };
54 | 6433E5C0184E83C2008EF278 /* InsuranceCompany.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InsuranceCompany.h; sourceTree = ""; };
55 | 6433E5C1184E83C2008EF278 /* InsuranceCompany.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InsuranceCompany.m; sourceTree = ""; };
56 | 6433E5C3184E83D6008EF278 /* Car.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Car.h; sourceTree = ""; };
57 | 6433E5C4184E83D6008EF278 /* Car.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Car.m; sourceTree = ""; };
58 | 892C1E90165D54160077F2CB /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; };
59 | 8956CE631754EE6A00BD551C /* MappingsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MappingsTests.m; sourceTree = ""; };
60 | 897AD0C51758D99A006869BA /* Person+Mappings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Person+Mappings.h"; path = "Categories/Person+Mappings.h"; sourceTree = ""; };
61 | 897AD0C61758D99A006869BA /* Person+Mappings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "Person+Mappings.m"; path = "Categories/Person+Mappings.m"; sourceTree = ""; };
62 | 897AD0C81758D9A6006869BA /* Car+Mappings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Car+Mappings.h"; path = "Categories/Car+Mappings.h"; sourceTree = ""; };
63 | 897AD0C91758D9A6006869BA /* Car+Mappings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "Car+Mappings.m"; path = "Categories/Car+Mappings.m"; sourceTree = ""; };
64 | 897D09FD15B0945400722A8F /* FindersAndCreatorsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FindersAndCreatorsTests.m; sourceTree = ""; };
65 | 8990C3151785C2F500212182 /* CoreDataManagerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CoreDataManagerTests.m; sourceTree = ""; };
66 | 89F1EEA315A4C73B00AE4FB4 /* SampleProject.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleProject.app; sourceTree = BUILT_PRODUCTS_DIR; };
67 | 89F1EEA715A4C73B00AE4FB4 /* UIKit.framework */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
68 | 89F1EEA915A4C73B00AE4FB4 /* Foundation.framework */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
69 | 89F1EEAB15A4C73B00AE4FB4 /* CoreGraphics.framework */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
70 | 89F1EEAD15A4C73B00AE4FB4 /* CoreData.framework */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
71 | 89F1EEB115A4C73B00AE4FB4 /* SampleProject-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SampleProject-Info.plist"; sourceTree = ""; };
72 | 89F1EEB315A4C73B00AE4FB4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; };
73 | 89F1EEB515A4C73B00AE4FB4 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
74 | 89F1EEB715A4C73B00AE4FB4 /* SampleProject-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SampleProject-Prefix.pch"; sourceTree = ""; };
75 | 89F1EEB815A4C73B00AE4FB4 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
76 | 89F1EEB915A4C73B00AE4FB4 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
77 | 89F1EEBC15A4C73B00AE4FB4 /* SampleProject.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = SampleProject.xcdatamodel; sourceTree = ""; };
78 | 89F1EEC315A4C73B00AE4FB4 /* SampleProjectTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SampleProjectTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
79 | 89F1EECD15A4C73B00AE4FB4 /* SampleProjectTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SampleProjectTests-Info.plist"; sourceTree = ""; };
80 | 89F1EECF15A4C73B00AE4FB4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; };
81 | A5F4AE16E7FA4739830EBD2A /* Pods-SampleProjectTests.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SampleProjectTests.xcconfig"; path = "Pods/Pods-SampleProjectTests.xcconfig"; sourceTree = SOURCE_ROOT; };
82 | C3D6081CC9E14BB688C96629 /* libPods-SampleProjectTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SampleProjectTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
83 | D5E6E703ADFB432282251ADF /* Pods.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.xcconfig; path = Pods/Pods.xcconfig; sourceTree = SOURCE_ROOT; };
84 | FA91A5E71875B9FD00BE74DD /* InsuranceCompany+Mappings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "InsuranceCompany+Mappings.h"; path = "Categories/InsuranceCompany+Mappings.h"; sourceTree = ""; };
85 | FA91A5E81875B9FD00BE74DD /* InsuranceCompany+Mappings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "InsuranceCompany+Mappings.m"; path = "Categories/InsuranceCompany+Mappings.m"; sourceTree = ""; };
86 | /* End PBXFileReference section */
87 |
88 | /* Begin PBXFrameworksBuildPhase section */
89 | 89F1EEA015A4C73B00AE4FB4 /* Frameworks */ = {
90 | isa = PBXFrameworksBuildPhase;
91 | buildActionMask = 2147483647;
92 | files = (
93 | 89F1EEA815A4C73B00AE4FB4 /* UIKit.framework in Frameworks */,
94 | 89F1EEAA15A4C73B00AE4FB4 /* Foundation.framework in Frameworks */,
95 | 89F1EEAC15A4C73B00AE4FB4 /* CoreGraphics.framework in Frameworks */,
96 | 89F1EEAE15A4C73B00AE4FB4 /* CoreData.framework in Frameworks */,
97 | 9701BF4FCD84477FA76795B9 /* libPods.a in Frameworks */,
98 | );
99 | runOnlyForDeploymentPostprocessing = 0;
100 | };
101 | 89F1EEBF15A4C73B00AE4FB4 /* Frameworks */ = {
102 | isa = PBXFrameworksBuildPhase;
103 | buildActionMask = 2147483647;
104 | files = (
105 | 89F1EEC615A4C73B00AE4FB4 /* UIKit.framework in Frameworks */,
106 | 89F1EEC715A4C73B00AE4FB4 /* Foundation.framework in Frameworks */,
107 | 89F1EEC815A4C73B00AE4FB4 /* CoreData.framework in Frameworks */,
108 | EBC606FFB7794B46B70E85E5 /* libPods-SampleProjectTests.a in Frameworks */,
109 | );
110 | runOnlyForDeploymentPostprocessing = 0;
111 | };
112 | /* End PBXFrameworksBuildPhase section */
113 |
114 | /* Begin PBXGroup section */
115 | 897D09F715B093F200722A8F /* Models */ = {
116 | isa = PBXGroup;
117 | children = (
118 | 6433E5C3184E83D6008EF278 /* Car.h */,
119 | 6433E5C4184E83D6008EF278 /* Car.m */,
120 | 6433E5C0184E83C2008EF278 /* InsuranceCompany.h */,
121 | 6433E5C1184E83C2008EF278 /* InsuranceCompany.m */,
122 | 6433E5B7184D89EC008EF278 /* Person.h */,
123 | 6433E5B8184D89EC008EF278 /* Person.m */,
124 | 496F953B174CCCBA00220FD1 /* OBRPerson.h */,
125 | 496F953C174CCCBA00220FD1 /* OBRPerson.m */,
126 | 897AD0C51758D99A006869BA /* Person+Mappings.h */,
127 | 897AD0C61758D99A006869BA /* Person+Mappings.m */,
128 | 897AD0C81758D9A6006869BA /* Car+Mappings.h */,
129 | 897AD0C91758D9A6006869BA /* Car+Mappings.m */,
130 | FA91A5E71875B9FD00BE74DD /* InsuranceCompany+Mappings.h */,
131 | FA91A5E81875B9FD00BE74DD /* InsuranceCompany+Mappings.m */,
132 | );
133 | path = Models;
134 | sourceTree = "";
135 | };
136 | 89F1EE9815A4C73B00AE4FB4 = {
137 | isa = PBXGroup;
138 | children = (
139 | 892C1E90165D54160077F2CB /* Default-568h@2x.png */,
140 | 89F1EEAF15A4C73B00AE4FB4 /* SampleProject */,
141 | 89F1EECB15A4C73B00AE4FB4 /* SampleProjectTests */,
142 | 89F1EEA615A4C73B00AE4FB4 /* Frameworks */,
143 | 89F1EEA415A4C73B00AE4FB4 /* Products */,
144 | D5E6E703ADFB432282251ADF /* Pods.xcconfig */,
145 | A5F4AE16E7FA4739830EBD2A /* Pods-SampleProjectTests.xcconfig */,
146 | );
147 | indentWidth = 4;
148 | sourceTree = "";
149 | tabWidth = 4;
150 | };
151 | 89F1EEA415A4C73B00AE4FB4 /* Products */ = {
152 | isa = PBXGroup;
153 | children = (
154 | 89F1EEA315A4C73B00AE4FB4 /* SampleProject.app */,
155 | 89F1EEC315A4C73B00AE4FB4 /* SampleProjectTests.xctest */,
156 | );
157 | name = Products;
158 | sourceTree = "";
159 | };
160 | 89F1EEA615A4C73B00AE4FB4 /* Frameworks */ = {
161 | isa = PBXGroup;
162 | children = (
163 | 89F1EEA715A4C73B00AE4FB4 /* UIKit.framework */,
164 | 89F1EEA915A4C73B00AE4FB4 /* Foundation.framework */,
165 | 89F1EEAB15A4C73B00AE4FB4 /* CoreGraphics.framework */,
166 | 89F1EEAD15A4C73B00AE4FB4 /* CoreData.framework */,
167 | 0EE71E2B92554B48A0914E08 /* libPods.a */,
168 | C3D6081CC9E14BB688C96629 /* libPods-SampleProjectTests.a */,
169 | );
170 | name = Frameworks;
171 | sourceTree = "";
172 | };
173 | 89F1EEAF15A4C73B00AE4FB4 /* SampleProject */ = {
174 | isa = PBXGroup;
175 | children = (
176 | 897D09F715B093F200722A8F /* Models */,
177 | 89F1EEB815A4C73B00AE4FB4 /* AppDelegate.h */,
178 | 89F1EEB915A4C73B00AE4FB4 /* AppDelegate.m */,
179 | 89F1EEBB15A4C73B00AE4FB4 /* SampleProject.xcdatamodeld */,
180 | 89F1EEB015A4C73B00AE4FB4 /* Supporting Files */,
181 | );
182 | path = SampleProject;
183 | sourceTree = "";
184 | };
185 | 89F1EEB015A4C73B00AE4FB4 /* Supporting Files */ = {
186 | isa = PBXGroup;
187 | children = (
188 | 89F1EEB115A4C73B00AE4FB4 /* SampleProject-Info.plist */,
189 | 89F1EEB215A4C73B00AE4FB4 /* InfoPlist.strings */,
190 | 89F1EEB515A4C73B00AE4FB4 /* main.m */,
191 | 89F1EEB715A4C73B00AE4FB4 /* SampleProject-Prefix.pch */,
192 | );
193 | name = "Supporting Files";
194 | sourceTree = "";
195 | };
196 | 89F1EECB15A4C73B00AE4FB4 /* SampleProjectTests */ = {
197 | isa = PBXGroup;
198 | children = (
199 | 89F1EECC15A4C73B00AE4FB4 /* Supporting Files */,
200 | 897D09FD15B0945400722A8F /* FindersAndCreatorsTests.m */,
201 | 8956CE631754EE6A00BD551C /* MappingsTests.m */,
202 | 8990C3151785C2F500212182 /* CoreDataManagerTests.m */,
203 | );
204 | path = SampleProjectTests;
205 | sourceTree = "";
206 | };
207 | 89F1EECC15A4C73B00AE4FB4 /* Supporting Files */ = {
208 | isa = PBXGroup;
209 | children = (
210 | 89F1EECD15A4C73B00AE4FB4 /* SampleProjectTests-Info.plist */,
211 | 89F1EECE15A4C73B00AE4FB4 /* InfoPlist.strings */,
212 | );
213 | name = "Supporting Files";
214 | sourceTree = "";
215 | };
216 | /* End PBXGroup section */
217 |
218 | /* Begin PBXNativeTarget section */
219 | 89F1EEA215A4C73B00AE4FB4 /* SampleProject */ = {
220 | isa = PBXNativeTarget;
221 | buildConfigurationList = 89F1EED615A4C73B00AE4FB4 /* Build configuration list for PBXNativeTarget "SampleProject" */;
222 | buildPhases = (
223 | 41FCE4D9B4F643A588FA6761 /* Check Pods Manifest.lock */,
224 | 89F1EE9F15A4C73B00AE4FB4 /* Sources */,
225 | 89F1EEA015A4C73B00AE4FB4 /* Frameworks */,
226 | 89F1EEA115A4C73B00AE4FB4 /* Resources */,
227 | 088021C66C364C3FA72F736C /* Copy Pods Resources */,
228 | );
229 | buildRules = (
230 | );
231 | dependencies = (
232 | );
233 | name = SampleProject;
234 | productName = SampleProject;
235 | productReference = 89F1EEA315A4C73B00AE4FB4 /* SampleProject.app */;
236 | productType = "com.apple.product-type.application";
237 | };
238 | 89F1EEC215A4C73B00AE4FB4 /* SampleProjectTests */ = {
239 | isa = PBXNativeTarget;
240 | buildConfigurationList = 89F1EED915A4C73B00AE4FB4 /* Build configuration list for PBXNativeTarget "SampleProjectTests" */;
241 | buildPhases = (
242 | 7643B274F0E941AFA2CAE2EB /* Check Pods Manifest.lock */,
243 | 89F1EEBE15A4C73B00AE4FB4 /* Sources */,
244 | 89F1EEBF15A4C73B00AE4FB4 /* Frameworks */,
245 | 89F1EEC015A4C73B00AE4FB4 /* Resources */,
246 | 89F1EEC115A4C73B00AE4FB4 /* ShellScript */,
247 | 3681CEA2F10B439AABF4E693 /* Copy Pods Resources */,
248 | );
249 | buildRules = (
250 | );
251 | dependencies = (
252 | 89F1EECA15A4C73B00AE4FB4 /* PBXTargetDependency */,
253 | );
254 | name = SampleProjectTests;
255 | productName = SampleProjectTests;
256 | productReference = 89F1EEC315A4C73B00AE4FB4 /* SampleProjectTests.xctest */;
257 | productType = "com.apple.product-type.bundle.unit-test";
258 | };
259 | /* End PBXNativeTarget section */
260 |
261 | /* Begin PBXProject section */
262 | 89F1EE9A15A4C73B00AE4FB4 /* Project object */ = {
263 | isa = PBXProject;
264 | attributes = {
265 | LastTestingUpgradeCheck = 0510;
266 | LastUpgradeCheck = 0500;
267 | };
268 | buildConfigurationList = 89F1EE9D15A4C73B00AE4FB4 /* Build configuration list for PBXProject "SampleProject" */;
269 | compatibilityVersion = "Xcode 3.2";
270 | developmentRegion = English;
271 | hasScannedForEncodings = 0;
272 | knownRegions = (
273 | en,
274 | );
275 | mainGroup = 89F1EE9815A4C73B00AE4FB4;
276 | productRefGroup = 89F1EEA415A4C73B00AE4FB4 /* Products */;
277 | projectDirPath = "";
278 | projectRoot = "";
279 | targets = (
280 | 89F1EEA215A4C73B00AE4FB4 /* SampleProject */,
281 | 89F1EEC215A4C73B00AE4FB4 /* SampleProjectTests */,
282 | );
283 | };
284 | /* End PBXProject section */
285 |
286 | /* Begin PBXResourcesBuildPhase section */
287 | 89F1EEA115A4C73B00AE4FB4 /* Resources */ = {
288 | isa = PBXResourcesBuildPhase;
289 | buildActionMask = 2147483647;
290 | files = (
291 | 89F1EEB415A4C73B00AE4FB4 /* InfoPlist.strings in Resources */,
292 | 892C1E91165D54160077F2CB /* Default-568h@2x.png in Resources */,
293 | );
294 | runOnlyForDeploymentPostprocessing = 0;
295 | };
296 | 89F1EEC015A4C73B00AE4FB4 /* Resources */ = {
297 | isa = PBXResourcesBuildPhase;
298 | buildActionMask = 2147483647;
299 | files = (
300 | 89F1EED015A4C73B00AE4FB4 /* InfoPlist.strings in Resources */,
301 | );
302 | runOnlyForDeploymentPostprocessing = 0;
303 | };
304 | /* End PBXResourcesBuildPhase section */
305 |
306 | /* Begin PBXShellScriptBuildPhase section */
307 | 088021C66C364C3FA72F736C /* Copy Pods Resources */ = {
308 | isa = PBXShellScriptBuildPhase;
309 | buildActionMask = 2147483647;
310 | files = (
311 | );
312 | inputPaths = (
313 | );
314 | name = "Copy Pods Resources";
315 | outputPaths = (
316 | );
317 | runOnlyForDeploymentPostprocessing = 0;
318 | shellPath = /bin/sh;
319 | shellScript = "\"${SRCROOT}/Pods/Pods-resources.sh\"\n";
320 | };
321 | 3681CEA2F10B439AABF4E693 /* Copy Pods Resources */ = {
322 | isa = PBXShellScriptBuildPhase;
323 | buildActionMask = 2147483647;
324 | files = (
325 | );
326 | inputPaths = (
327 | );
328 | name = "Copy Pods Resources";
329 | outputPaths = (
330 | );
331 | runOnlyForDeploymentPostprocessing = 0;
332 | shellPath = /bin/sh;
333 | shellScript = "\"${SRCROOT}/Pods/Pods-SampleProjectTests-resources.sh\"\n";
334 | };
335 | 41FCE4D9B4F643A588FA6761 /* Check Pods Manifest.lock */ = {
336 | isa = PBXShellScriptBuildPhase;
337 | buildActionMask = 2147483647;
338 | files = (
339 | );
340 | inputPaths = (
341 | );
342 | name = "Check Pods Manifest.lock";
343 | outputPaths = (
344 | );
345 | runOnlyForDeploymentPostprocessing = 0;
346 | shellPath = /bin/sh;
347 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
348 | };
349 | 7643B274F0E941AFA2CAE2EB /* Check Pods Manifest.lock */ = {
350 | isa = PBXShellScriptBuildPhase;
351 | buildActionMask = 2147483647;
352 | files = (
353 | );
354 | inputPaths = (
355 | );
356 | name = "Check Pods Manifest.lock";
357 | outputPaths = (
358 | );
359 | runOnlyForDeploymentPostprocessing = 0;
360 | shellPath = /bin/sh;
361 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
362 | };
363 | 89F1EEC115A4C73B00AE4FB4 /* ShellScript */ = {
364 | isa = PBXShellScriptBuildPhase;
365 | buildActionMask = 2147483647;
366 | files = (
367 | );
368 | inputPaths = (
369 | );
370 | outputPaths = (
371 | );
372 | runOnlyForDeploymentPostprocessing = 0;
373 | shellPath = /bin/sh;
374 | shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n";
375 | };
376 | /* End PBXShellScriptBuildPhase section */
377 |
378 | /* Begin PBXSourcesBuildPhase section */
379 | 89F1EE9F15A4C73B00AE4FB4 /* Sources */ = {
380 | isa = PBXSourcesBuildPhase;
381 | buildActionMask = 2147483647;
382 | files = (
383 | 6433E5C5184E83D6008EF278 /* Car.m in Sources */,
384 | 89F1EEB615A4C73B00AE4FB4 /* main.m in Sources */,
385 | 89F1EEBA15A4C73B00AE4FB4 /* AppDelegate.m in Sources */,
386 | FA91A5E91875B9FE00BE74DD /* InsuranceCompany+Mappings.m in Sources */,
387 | 6433E5B9184D89EC008EF278 /* Person.m in Sources */,
388 | 6433E5C2184E83C2008EF278 /* InsuranceCompany.m in Sources */,
389 | 89F1EEBD15A4C73B00AE4FB4 /* SampleProject.xcdatamodeld in Sources */,
390 | 496F953D174CCCBA00220FD1 /* OBRPerson.m in Sources */,
391 | 897AD0C71758D99A006869BA /* Person+Mappings.m in Sources */,
392 | 897AD0CA1758D9A6006869BA /* Car+Mappings.m in Sources */,
393 | );
394 | runOnlyForDeploymentPostprocessing = 0;
395 | };
396 | 89F1EEBE15A4C73B00AE4FB4 /* Sources */ = {
397 | isa = PBXSourcesBuildPhase;
398 | buildActionMask = 2147483647;
399 | files = (
400 | 897D09FE15B0945400722A8F /* FindersAndCreatorsTests.m in Sources */,
401 | 89702F25173E57AA00149BD5 /* SampleProject.xcdatamodeld in Sources */,
402 | 8956CE641754EE6A00BD551C /* MappingsTests.m in Sources */,
403 | 8990C3161785C2F500212182 /* CoreDataManagerTests.m in Sources */,
404 | );
405 | runOnlyForDeploymentPostprocessing = 0;
406 | };
407 | /* End PBXSourcesBuildPhase section */
408 |
409 | /* Begin PBXTargetDependency section */
410 | 89F1EECA15A4C73B00AE4FB4 /* PBXTargetDependency */ = {
411 | isa = PBXTargetDependency;
412 | target = 89F1EEA215A4C73B00AE4FB4 /* SampleProject */;
413 | targetProxy = 89F1EEC915A4C73B00AE4FB4 /* PBXContainerItemProxy */;
414 | };
415 | /* End PBXTargetDependency section */
416 |
417 | /* Begin PBXVariantGroup section */
418 | 89F1EEB215A4C73B00AE4FB4 /* InfoPlist.strings */ = {
419 | isa = PBXVariantGroup;
420 | children = (
421 | 89F1EEB315A4C73B00AE4FB4 /* en */,
422 | );
423 | name = InfoPlist.strings;
424 | sourceTree = "";
425 | };
426 | 89F1EECE15A4C73B00AE4FB4 /* InfoPlist.strings */ = {
427 | isa = PBXVariantGroup;
428 | children = (
429 | 89F1EECF15A4C73B00AE4FB4 /* en */,
430 | );
431 | name = InfoPlist.strings;
432 | sourceTree = "";
433 | };
434 | /* End PBXVariantGroup section */
435 |
436 | /* Begin XCBuildConfiguration section */
437 | 89F1EED415A4C73B00AE4FB4 /* Debug */ = {
438 | isa = XCBuildConfiguration;
439 | buildSettings = {
440 | ALWAYS_SEARCH_USER_PATHS = NO;
441 | CLANG_ENABLE_OBJC_ARC = YES;
442 | CLANG_WARN_BOOL_CONVERSION = YES;
443 | CLANG_WARN_CONSTANT_CONVERSION = YES;
444 | CLANG_WARN_EMPTY_BODY = YES;
445 | CLANG_WARN_ENUM_CONVERSION = YES;
446 | CLANG_WARN_INT_CONVERSION = YES;
447 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
448 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
449 | COPY_PHASE_STRIP = NO;
450 | GCC_C_LANGUAGE_STANDARD = gnu99;
451 | GCC_DYNAMIC_NO_PIC = NO;
452 | GCC_OPTIMIZATION_LEVEL = 0;
453 | GCC_PREPROCESSOR_DEFINITIONS = (
454 | "DEBUG=1",
455 | "$(inherited)",
456 | );
457 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
458 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
459 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
460 | GCC_WARN_ABOUT_RETURN_TYPE = YES;
461 | GCC_WARN_UNDECLARED_SELECTOR = YES;
462 | GCC_WARN_UNINITIALIZED_AUTOS = YES;
463 | GCC_WARN_UNUSED_FUNCTION = YES;
464 | GCC_WARN_UNUSED_VARIABLE = YES;
465 | IPHONEOS_DEPLOYMENT_TARGET = 4.3;
466 | ONLY_ACTIVE_ARCH = YES;
467 | SDKROOT = iphoneos;
468 | };
469 | name = Debug;
470 | };
471 | 89F1EED515A4C73B00AE4FB4 /* Release */ = {
472 | isa = XCBuildConfiguration;
473 | buildSettings = {
474 | ALWAYS_SEARCH_USER_PATHS = NO;
475 | CLANG_ENABLE_OBJC_ARC = YES;
476 | CLANG_WARN_BOOL_CONVERSION = YES;
477 | CLANG_WARN_CONSTANT_CONVERSION = YES;
478 | CLANG_WARN_EMPTY_BODY = YES;
479 | CLANG_WARN_ENUM_CONVERSION = YES;
480 | CLANG_WARN_INT_CONVERSION = YES;
481 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
482 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
483 | COPY_PHASE_STRIP = YES;
484 | GCC_C_LANGUAGE_STANDARD = gnu99;
485 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
486 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
487 | GCC_WARN_ABOUT_RETURN_TYPE = YES;
488 | GCC_WARN_UNDECLARED_SELECTOR = YES;
489 | GCC_WARN_UNINITIALIZED_AUTOS = YES;
490 | GCC_WARN_UNUSED_FUNCTION = YES;
491 | GCC_WARN_UNUSED_VARIABLE = YES;
492 | IPHONEOS_DEPLOYMENT_TARGET = 4.3;
493 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1";
494 | SDKROOT = iphoneos;
495 | VALIDATE_PRODUCT = YES;
496 | };
497 | name = Release;
498 | };
499 | 89F1EED715A4C73B00AE4FB4 /* Debug */ = {
500 | isa = XCBuildConfiguration;
501 | baseConfigurationReference = D5E6E703ADFB432282251ADF /* Pods.xcconfig */;
502 | buildSettings = {
503 | CODE_SIGN_IDENTITY = "iPhone Developer";
504 | GCC_PRECOMPILE_PREFIX_HEADER = YES;
505 | GCC_PREFIX_HEADER = "SampleProject/SampleProject-Prefix.pch";
506 | INFOPLIST_FILE = "SampleProject/SampleProject-Info.plist";
507 | PRODUCT_NAME = "$(TARGET_NAME)";
508 | USER_HEADER_SEARCH_PATHS = "";
509 | WRAPPER_EXTENSION = app;
510 | };
511 | name = Debug;
512 | };
513 | 89F1EED815A4C73B00AE4FB4 /* Release */ = {
514 | isa = XCBuildConfiguration;
515 | baseConfigurationReference = D5E6E703ADFB432282251ADF /* Pods.xcconfig */;
516 | buildSettings = {
517 | CODE_SIGN_IDENTITY = "iPhone Developer";
518 | GCC_PRECOMPILE_PREFIX_HEADER = YES;
519 | GCC_PREFIX_HEADER = "SampleProject/SampleProject-Prefix.pch";
520 | INFOPLIST_FILE = "SampleProject/SampleProject-Info.plist";
521 | PRODUCT_NAME = "$(TARGET_NAME)";
522 | USER_HEADER_SEARCH_PATHS = "";
523 | WRAPPER_EXTENSION = app;
524 | };
525 | name = Release;
526 | };
527 | 89F1EEDA15A4C73B00AE4FB4 /* Debug */ = {
528 | isa = XCBuildConfiguration;
529 | baseConfigurationReference = A5F4AE16E7FA4739830EBD2A /* Pods-SampleProjectTests.xcconfig */;
530 | buildSettings = {
531 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/SampleProject.app/SampleProject";
532 | FRAMEWORK_SEARCH_PATHS = (
533 | "$(SDKROOT)/Developer/Library/Frameworks",
534 | "$(inherited)",
535 | );
536 | GCC_PRECOMPILE_PREFIX_HEADER = YES;
537 | GCC_PREFIX_HEADER = "SampleProject/SampleProject-Prefix.pch";
538 | INFOPLIST_FILE = "SampleProjectTests/SampleProjectTests-Info.plist";
539 | PRODUCT_NAME = "$(TARGET_NAME)";
540 | TEST_HOST = "$(BUNDLE_LOADER)";
541 | };
542 | name = Debug;
543 | };
544 | 89F1EEDB15A4C73B00AE4FB4 /* Release */ = {
545 | isa = XCBuildConfiguration;
546 | baseConfigurationReference = A5F4AE16E7FA4739830EBD2A /* Pods-SampleProjectTests.xcconfig */;
547 | buildSettings = {
548 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/SampleProject.app/SampleProject";
549 | FRAMEWORK_SEARCH_PATHS = (
550 | "$(SDKROOT)/Developer/Library/Frameworks",
551 | "$(inherited)",
552 | );
553 | GCC_PRECOMPILE_PREFIX_HEADER = YES;
554 | GCC_PREFIX_HEADER = "SampleProject/SampleProject-Prefix.pch";
555 | INFOPLIST_FILE = "SampleProjectTests/SampleProjectTests-Info.plist";
556 | PRODUCT_NAME = "$(TARGET_NAME)";
557 | TEST_HOST = "$(BUNDLE_LOADER)";
558 | };
559 | name = Release;
560 | };
561 | /* End XCBuildConfiguration section */
562 |
563 | /* Begin XCConfigurationList section */
564 | 89F1EE9D15A4C73B00AE4FB4 /* Build configuration list for PBXProject "SampleProject" */ = {
565 | isa = XCConfigurationList;
566 | buildConfigurations = (
567 | 89F1EED415A4C73B00AE4FB4 /* Debug */,
568 | 89F1EED515A4C73B00AE4FB4 /* Release */,
569 | );
570 | defaultConfigurationIsVisible = 0;
571 | defaultConfigurationName = Release;
572 | };
573 | 89F1EED615A4C73B00AE4FB4 /* Build configuration list for PBXNativeTarget "SampleProject" */ = {
574 | isa = XCConfigurationList;
575 | buildConfigurations = (
576 | 89F1EED715A4C73B00AE4FB4 /* Debug */,
577 | 89F1EED815A4C73B00AE4FB4 /* Release */,
578 | );
579 | defaultConfigurationIsVisible = 0;
580 | defaultConfigurationName = Release;
581 | };
582 | 89F1EED915A4C73B00AE4FB4 /* Build configuration list for PBXNativeTarget "SampleProjectTests" */ = {
583 | isa = XCConfigurationList;
584 | buildConfigurations = (
585 | 89F1EEDA15A4C73B00AE4FB4 /* Debug */,
586 | 89F1EEDB15A4C73B00AE4FB4 /* Release */,
587 | );
588 | defaultConfigurationIsVisible = 0;
589 | defaultConfigurationName = Release;
590 | };
591 | /* End XCConfigurationList section */
592 |
593 | /* Begin XCVersionGroup section */
594 | 89F1EEBB15A4C73B00AE4FB4 /* SampleProject.xcdatamodeld */ = {
595 | isa = XCVersionGroup;
596 | children = (
597 | 89F1EEBC15A4C73B00AE4FB4 /* SampleProject.xcdatamodel */,
598 | );
599 | currentVersion = 89F1EEBC15A4C73B00AE4FB4 /* SampleProject.xcdatamodel */;
600 | path = SampleProject.xcdatamodeld;
601 | sourceTree = "";
602 | versionGroupType = wrapper.xcdatamodel;
603 | };
604 | /* End XCVersionGroup section */
605 | };
606 | rootObject = 89F1EE9A15A4C73B00AE4FB4 /* Project object */;
607 | }
608 |
--------------------------------------------------------------------------------