VinylRecord(简称Vinyl ActiveRecord)是一款纯SQLite ActiveRecord ORM,用于iOS,且不含CoreData。它是已停止的iActiveRecord项目的分支,由Alex Denisov创立,他在创建iOS ORM方面迈出了重大第一步,当时网站上的其他ORM非常少。为了快速修复和新特性以满足需要持久层的应用程序的需求,该API以新名称重生,以避免与其前辈混淆,同时保持与前辈的兼容性,并与其他带ActiveRecord名称的ORM保持一致。
# Podfile
pod 'VinylRecord', '1.0.9'
git clone https://github.com/valerius/VinylRecord.git
在您的应用程序加载时,您应该初始化数据库。请注意,配置只会发生一次,因此如果您使用CocoaPods,其中VinylRecord作为依赖项,您应该在利用CocoaPods库初始化程序(如果有的话)之前初始化您的数据库,否则您的设置将不会产生影响。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[VinylRecord applyConfiguration:^(ARConfiguration *config) {
config.databasePath = ARCachesDatabasePath(nil); //creates default database path
config.enableThreadPool = YES; //Enables a database connection per thread
config.migrationsEnabled = YES; //Enables simple migrations
}];
}
要创建一个模型,您应该将VinylRecord类作为子类,它是它前辈ActiveRecord类的后代,并且它与它保持兼容。当您首次运行应用程序时,框架将自动使用从VinylRecord继承的类创建数据库表。
所以基于以下代码
#import <ActiveRecord/VinylRecord.h> // when not using CocoaPods
#import "VinylRecord.h" // when using CocoaPods
@interface User : VinylRecord
column_dec(string,name)
column_dec(boolean,active)
// or @property (nonatomic, retain) NSString *name;
@end
@implementation User
column_imp(string,name) // or @dynamic
column_imp(boolean,active) // user.is_active = YES or user.has_active not available using @dynamic
@end
将创建名为'user'的表。它将包含'name'和'active'字段。
在描述模型之后,您可以使用该框架保存和查询对象
User *user = [User new];
user.name = @"Alex";
[user save];
NSArray *users = [User allRecords];
User *userForRemove = [users first];
[userForRemove dropRecord];
此代码创建,检索列表并删除模型。
那么不应该存储在数据库中的属性怎么办?目前,所有未标记为 @dynamic 的属性都被忽略。
VinylRecord 支持 SQL 中的典型数据类型,这些类型可以使用简单的宏定义(推荐)或使用带有相应 @dynamic 属性的标准属性定义,在实现中进行定义。因为它们有助于您更容易地在数据库列和某些情况下提供额外的辅助方法,所以优先考虑宏。
@interface DataTypes : VinylRecord
column_dec(string,property_of_string) // NSString
column_dec(integer,property_of_integer) // NSNumber
column_dec(boolean,property_of_boolean) // NSNumber
column_dec(decimal,property_of_decimal) // NSDecimalNumber
column_dec(blob,property_of_blob) // NSData
column_dec(date,property_of_date) // NSDate
column_dec(dictionary,property_of_dictionary) // NSMutableDictionary
column_dec(array,property_of_array) // NSMutableArray
@end
@implementation DataTypes
column_imp(string,property_of_string)
column_imp(integer,property_of_integer) // dataTypes.int_property_of_integer = 10; or dataTypes.int_property_of_integer = @(10)
column_imp(boolean,property_of_bool) // dataTypes.has_property_of_bool,is_property_of_bool or [dataTypes.property_of_bool booleanValue]
column_imp(decimal,property_of_decimal)
column_imp(blob,property_of_blob)
column_imp(date,property_of_date)
column_imp(dictionary,property_of_dictionary)
column_imp(array,property_of_array)
// or @dynamic property_name which doesn't generate helpers
@end
可以使用模型实现中的验证_do 辅助宏注册验证。
// User.h
@interface User : VinylRecord <ARValidatorProtocol>
column_dec(string,name)
@end
// User.m
@implementation User
column_imp(string,name)
validation_do(
validate_uniqueness_of(name)
validate_presence_of(name)
)
@end
标准验证,如 validate_uniqueness_of 和 validate_presence_of 将具有现有帮助程序,但您也可以添加自己的自定义验证。只需定义一个新的类并按如下方式实现:
// Custom validator
// PrefixValidator.h
@interface PrefixValidator : NSObject <ARValidatorProtocol>
@end
// PrefixValidator.m
@implementation PrefixValidator
- (NSString *)errorMessage {
return @"Invalid prefix";
}
- (BOOL)validateField:(NSString *)aField ofRecord:(id)aRecord {
NSString *aValue = [aRecord valueForKey:aField];
BOOL valid = [aValue hasPrefix:@"LOL"];
return valid;
}
@end
然后将验证器添加到您的 ActiveRecord 实现
@implementation User
validation_do(
validate_field_with_validator(name, PrefixValidator)
)
@end
验证错误
User *user = [User new];
if(![user isValid]){
NSArray *errors = [user errorMessages];
// do something
}
// or
User *user = [User new];
if(![user save]){
NSArray *errors = [user errorMessages];
// do something
}
VinylRecords 支持 BelongsTo, HasMany, 和 HasManyThrough 映射关系,带有 "ON DELETE" 绑定依赖关系,可以是 DESTROY 或 NULLIFY,可以使用相应的宏来指定。
belongs_to 关联会在另一个模型之间建立一对一连接,因此声明模型的每个实例 "属于" 另一个模型的实例。例如,如果您的应用程序包含用户和组,并且每个用户只能分配给一个组,则可以这样声明模型
// User.h
@interface User : VinylRecord
belongs_to_dec(Group, group, ARDependencyDestroy) // or ARDependencyNullify
// or belongs_to_dec(Group, group, groupId, ARDependencyDestroy) // groupId is automatic in above example
column_dec(string,name)
@end
// User.m
@implementation User
belongs_to_imp(Group, group, ARDependencyDestroy)
// or belongs_to_imp(Group, group, groupId, ARDependencyDestroy) // groupId is automatic in above example
column_imp(string,name)
@end
在上面的例子中,belongs_to_dec 和 belongs_to_imp 需要三个或四个参数:模型类名('Group'),getter 名称('group'),可选的列名(通常为 getter 名称加上 'Id' 后缀),之后是删除对象的 ARDependencyDestroy 方法,或销毁关联父对象的 ARDependencyNullify 方法(例如 Group)。在描述关联键的列时,如果默认值不足,主要需要注意的是,它必须与关联类的名称匹配,并以小写字母开头,后面跟着 'Id' 后缀。以下是一些例子
Group <-> groupId
User <-> userId
ContentManager <-> contentManagerId
注意:虽然 VinylRecord 几乎与前辈完全兼容,但这里的区别在于您不再需要单独指定键,逻辑是,由于键始终需要,因此总是由宏生成。因此,在最坏的情况下,如果您遇到编译错误,说明属性已经定义,您只需要删除由宏生成的重复属性。
has_many 关联表示与另一个模型的一对多连接。您通常会在 belongs_to 关联的 "另一边" 找到这种关联。这种关联表示模型实例包含零个或多个其他模型实例。与前一个示例相关的 Group 模型可以这样声明:
// Group.h
@interface Group : VinylRecord
has_many_dec(User, users, ARDependencyDestroy)
column_dec(string,title)
@end
// Group.m
@implementation Group
has_many_imp(User, users, ARDependencyDestroy)
column_imp(string,title)
@end
此外,这些宏为以下格式添加关联对象生成辅助方法
add##ModelName:(ActiveRecord *)aRecord;
remove##ModelName:(ActiveRecord *)aRecord;
因此,您可以将用户添加到以下组中
@implementation GroupTest
- (void) addStudents {
User *john = [User new];
john.name = @"John";
User *alex = [User new:@{"name": @"Alex"}]; // assign properties in constructor
Group *students = [Group new];
students.title = @"Students";
[students addUser:john];
[students addUser:alex];
NSAssert([students.users count] == 2,@"Cached students should == 2");
NSAssert([students save] == TRUE, @"Should save group and users");
NSAssert([students.users count] == 2,@"Persisted students count should == 2");
}
@end
has_many_through 关联通常用于设置与其他模型的许多对多连接。此关联表示声明模型可以通过“通过”第三模型与零个或多个其他模型的实例相匹配。
以下示例中,has_many_through 接收四个参数,要返回的关联类、用于参考关联两端的模型、用于从关联返回结果的获取器以及如果删除接收关联,则在“通过”模型上执行的操作。
// User.h
@interface User : VinylRecord
has_many_through_dec(Project, UserProjectRelationship, projects, ARDependencyDestroy)
column_dec(string,name)
@end
// User.m
@implementation User
has_many_through_imp(Project, UserProjectRelationship, projects, ARDependencyDestroy)
column_imp(string,name)
@end
// Project.h
@interface Project : VinylRecord
has_many_through_dec(User, UserProjectRelationship, users, ARDependencyDestroy)
column_dec(string,name)
@end
// Project.m
@implementation Project
has_many_through_imp(User, UserProjectRelationship, users, ARDependencyDestroy)
column_imp(string,name)
@end
为了完成此关系,您需要创建“通过”模型,以在两个方向上链接关联。
// UserProjectRelationship.h
@interface UserProjectRelationship : VinylRecord
column_dec(key,userId)
column_dec(key,projectId)
@end
// UserProjectRelationship.m
@implementation UserProjectRelationship
column_imp(key,userId)
column_imp(key,projectId)
@end
示例用法
@implementation HasManyThroughTest
- (void)testHasManyTrough {
User userJohn = [User new: @{"name":"john"}];
Project *makeTea = [Project new: {@"name": "Make tea"}];
[makeTea addUser:userJohn];
[makeTea save];
NSAssert([makeTea.users count] == 1, @"Project should have 1 user");
NSAssert([userJohn.projects count] == 1, @"User should have 1 project");
}
@end
VinylRecords 支持常规和保存点事务,现已具有线程安全功能,通过在每个访问数据库的线程上维护一个单独的数据库连接来实现 SQLite 的隔离和并发支持,这些连接在线程退出时自动关闭,并分别、顺序地使用 GCD 进行排队。这确保了在后台线程中回滚事务不会回滚在单独的线程上放置的不相关查询。您还可以嵌套深度到 SQLite 和您的手机允许的 SAVEPOINT 事务。
[VinylRecord transaction:^{
User *alex = [User new];
alex.name = @"Alex";
[alex save];
}];
[VinylRecord transaction:^{
User *alex = [User new];
alex.name = @"Alex";
[alex save];
ar_rollback;
}];
[VinylRecord savePointTransaction:^(ARTransactionState *state_a) {
... // A Changes.
BOOL success = [ActiveRecord savePointTransaction:^(ARTransactionState *state_b) {
... // These changes are folled back
ar_rollback_to(state_b);
}];
if(success) {
//we could rollback, or keep state_a change. To rollback: ar_rollback(state_a);
}
}];
VinylRecord 支持各种方法,允许查询记录,包括使用 where 语句、Objective-C 查询 whereField: 方法,以及预定义的 findBy 方法。
除了 findBy 辅助方法之外的所有方法都返回 ARLazyFetcher 的实例,允许您在单行中链接查询
NSArray *users = [[[[User query] offset:5] limit:2] fetchRecords];
fetchRecords 方法根据所有应用的条件对数据库进行 sql 查询
ARLazyFetcher *fetcher = [[[User query] offset:2] limit:10];
[fetcher whereField:@"name"
equalToValue:@"Alex"];
NSArray *users = [fetcher fetchRecords];
有一些选项可用于 'where' 方法
- (ARLazyFetcher *)whereField:(NSString *)aField equalToValue:(id)aValue;
- (ARLazyFetcher *)whereField:(NSString *)aField notEqualToValue:(id)aValue;
- (ARLazyFetcher *)whereField:(NSString *)aField in:(NSArray *)aValues;
- (ARLazyFetcher *)whereField:(NSString *)aField notIn:(NSArray *)aValues;
- (ARLazyFetcher *)whereField:(NSString *)aField like:(NSString *)aPattern;
- (ARLazyFetcher *)whereField:(NSString *)aField notLike:(NSString *)aPattern;
- (ARLazyFetcher *)whereField:(NSString *)aField between:(id)startValue and:(id)endValue;
您还可以将一些“复杂”或简单过滤器附加到 finalStatement 。
HasManyThrough 关系已经使用了 'where' 过滤器,因此如果需要添加更多,应使用如下所示的代码
NSArray *ids = [NSArray arrayWithObjects:
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:15],
nil];
ARLazyFetcher *fetcher = [[User query] where:@"'name' = %@ or 'id' in %@", @"john", ids, nil];
NSArray *records = [[fetcher orderBy:@"id"] fetchRecords];
SQLite SQL 比较操作
- (ARLazyFetcher *)only:(NSString *)aFirstParam, ...;
- (ARLazyFetcher *)except:(NSString *)aFirstParam, ...;
ARLazyFetcher *fetcher = [[User query] only:@"name", @"id", nil];
NSArray *users = [fetcher fetchRecords];
- (ARLazyFetcher *)orderBy:(NSString *)aField ascending:(BOOL)isAscending;
- (ARLazyFetcher *)orderBy:(NSString *)aField;// ASC by default
ARLazyFetcher *fetcher = [[[User query] offset:2] limit:10];
[[fetcher whereField:@"name"
equalToValue:@"Alex"] orderBy:@"name"];
NSArray *users = [fetcher fetchRecords];
框架支持使用连接
- (ARLazyFetcher *)join:(Class)aJoinRecord
useJoin:(ARJoinType)aJoinType
onField:(NSString *)aFirstField
andField:(NSString *)aSecondField;
aJoinRecord - 我们用于连接的模型
aJoinType - 连接类型,可能有几个选项
可以通过向 ARLazyFetcher 实例发送消息来获取已连接的记录
- (NSArray *)fetchJoinedRecords;
消息。
VinylRecord 支持简单的迁移,这将自动向已存在的表添加新表或新列,而不会丢失数据。如不需要迁移,请在应用启动时禁用它。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[VinylRecord applyConfiguration:^(ARConfiguration *config) {
config.migrationsEnabled = NO;
}];
}