VinylRecord 1.0.9

VinylRecord 1.0.9

测试已测试
Lang语言 CC
许可证 MIT
发布最后发布2015年10月

Valerius 维护。



  • James Whitfield

VinylRecord(简称Vinyl ActiveRecord)是一款纯SQLite ActiveRecord ORM,用于iOS,且不含CoreData。它是已停止的iActiveRecord项目的分支,由Alex Denisov创立,他在创建iOS ORM方面迈出了重大第一步,当时网站上的其他ORM非常少。为了快速修复和新特性以满足需要持久层的应用程序的需求,该API以新名称重生,以避免与其前辈混淆,同时保持与前辈的兼容性,并与其他带ActiveRecord名称的ORM保持一致。

特性

  • Belongs-To, Has-Many和Has-Many-Through关系
  • 嵌套SavePointTransactions(新功能)
  • 线程安全事务(新功能)
  • 模型的保存、更新和同步
  • 传统和自定义验证
  • 保存/更新回调(新功能)

即将推出

  • 官方Swift支持(目前通过子类效果最佳,但可通过模块导入使用。没有关联,不需要Objc-C)
  • 更好的文档:)

安装


# Podfile
pod 'VinylRecord', '1.0.9'

使用源码


    git clone https://github.com/valerius/VinylRecord.git
  1. 检出代码
  2. 使用XCode打开,并构建Build_Framework目标。
  3. ActiveRecord.framework将位于~/Products/,此路径可以在目标的设置标签页中的shell脚本中更改。
  4. 打开您的项目设置,并将ActiveRecord.framework和sqlite3.0.dylib添加到Link Binary With Libraries部分。

使用

初始化数据库

在您的应用程序加载时,您应该初始化数据库。请注意,配置只会发生一次,因此如果您使用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,可以使用相应的宏来指定。

HasMany <-> BelongsTo

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_decbelongs_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

HasManyThrough

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 比较操作

  • =, == - 等于
  • >= - 左操作数大于等于右操作数
  • <= - 右操作数大于等于左操作数
  • > - 左操作数大于右操作数
  • < - 右操作数大于左操作数
  • !=, <> - 操作数不相等
  • LIKE, NOT LIKE - 模式匹配
  • IN, NOT IN 容器 - 字段在容器(NSArray、NSSet 等)中的记录
  • BETWEEN %1 AND %2 - 字段在范围(%1, %2)内的记录

只获取必要字段

- (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 - 连接类型,可能有几个选项

  • ARJoinLeft
  • ARJoinRight
  • ARJoinInner
  • ARJoinOuter
    aFirstField - 当前模型将用于连接的字段
    aSecondField - aJoinModel 将用于连接的字段

可以通过向 ARLazyFetcher 实例发送消息来获取已连接的记录

- (NSArray *)fetchJoinedRecords;

消息。

迁移

VinylRecord 支持简单的迁移,这将自动向已存在的表添加新表或新列,而不会丢失数据。如不需要迁移,请在应用启动时禁用它。

    - (BOOL)application:(UIApplication *)application  didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        [VinylRecord applyConfiguration:^(ARConfiguration *config) {
                        config.migrationsEnabled = NO;
        }];
    }