├── .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 | --------------------------------------------------------------------------------