Writing your Orm for Android with Canasta and Senorit, Part 3

    Introduction


    After a break in the development of my Android application , during which more and more new ideas were forming in my head on how to make it more beautiful and more convenient, at the end of January I again sat down for development. During reflection, the approach to creating the application was slightly transformed, and therefore I got to the object model only three weeks ago. And almost immediately I was faced with the need to finalize UCAOrm . Who cares to learn not only about the innovations that have already been introduced, but also about what is still only in the development process -

    Changes and additions


    The first thing I came across was the need for ContentProvider and Cursor .
    There were n’t any particular problems with the ContentProvider - the abstract OrmContentProvider inherits from ContentProvider and implements two methods so far: query , which accepts OrmWhere and returns OrmCursor , and update , which accepts the updated instance. OrmCursor is inherited from AbstractCursor and, in addition to implementing all the necessary methods, also implements the getEntities method - returning a Listobjects. The most interesting, from the implementation point of view, are the getColumnNames function , which returns an array of column names (the getOrmFields function has already been redone), and the private getObject function , which returns the value of the specified column. These classes have greatly simplified the development of a synchronization account.
    The second innovation was support for new field types: boolean and int array . If everything is more or less clear with boolean , then I ’ll tell you a bit more about array . First, the idea came up to create an additional table with the name “ class_name field name”And one single column of the type of the component of the array. However, having reasoned, I came to the conclusion that an array with a class inherited from OrmEntity destroys the entire architecture, and the developer will have to serialize manually any other non-primitive type. From here, I decided that orm would only support arrays of primitive types that perfectly serialize to a string and also perfectly deserialize back. Problems, perhaps, can arise with a double , the format of which in the form of a string can contain a comma, which is a separator of the elements of the array, but they are easily solved by hard setting the locale in English .
    Also, finally got to the implementation of the getDefaultValues method in the heirOrmHelper 'a. Now it looks like this:
        @Override
        public void getDefaultValues(Class entityClass, List valueList) {
        }
    

    accordingly, adding default values ​​for our favorite model from the second part will be implemented as follows:
       public void getDefaultValues(Class entityClass, List valueList) {
            if (entityClass.equals(CarType.class)) {
                valueList.add(new CarType("Passenger"));
                valueList.add(new CarType("Truck"));
            }
        }
    

    Well, now we got to the most delicious problem that hardex talked about in the first article - updating the data schema.

    Data Schema Update


    Again, back to our model and consider the essence of Car :
        @Table(rightJoinTo = {Truck.class})
        public class Car extends BaseEntity {
           @Column(name = "car_type")
           private CarType type;
           @Column
           private List wheels;
           @Column(name = "engine_power")
           private int enginePower;
           @Column(name = "doors_count")
           private int doorsCount;
        }
    

    Suppose we decide to add another field:
           @Column(name = "max_speed")
           private int maxSpeed;
    

    In this case, we need to change the version of the database in manifest:

    And write the code in the onUpdate helper method :
       @Override
        protected void onUpgrade(int oldVersion, int newVersion) {
    	    if (newVersion == 2) {
    		    OrmUtils.UpdateTable(Car.class).work();
    	    }
        }
    

    Why else do we need the work method ? ”- someone will ask. And let's look at possible options for changing the data scheme:
    1. A new field is added to the schema.
    2. The field is deleted from the schema.
    3. The field is renamed.
    4. The type of the field is changing.

    Most likely, many have already guessed that the only point that does not cause difficulties is the first, but we will consider them in order.

    Field Additions

    Everything is easy here: orm grabs the fields from the table and compares them with the fields from the class. When a new field is in the object model, it twitches
    ALTER TABLE … ADD COLUMN …
    
    If a default value is needed, then it will need to be indicated in the annotation.

    Field deletion

    The beginning of the algorithm is similar to the previous one: we compare the fields and find those that need to be deleted. Well, then, almost, as indicated in the faq . The only thing I don’t understand is why a second copy is needed, because after drop 'the zeros of the table, you can simply rename the temporary one and it will become permanent!

    Rename Field

    And here work is not your assistant! Orm simply will not understand that you simply renamed the field, and will do two things: remove the field with the old name from the database and add a new one with a new one. Of course, this could also be beaten in the annotation by adding the old_name field , but it seemed to me that this was already too much, and orm could be offloaded by telling him exactly what to do. In light of the above in this paragraph, we need the rename method :
    OrmUtils.UpdateTable(Car.class).renameColumn("old_column_name", "new_column_name");
    

    Note that you must specify the name of the column, not the field! As a result, orm will not wool the entire class and all the fields in the database in order to understand what it needs to change, but simply make changes to the name of one single column.
    We can also help him add a column:
    OrmUtils.UpdateTable(Car.class).addColumn("column_name");
    

    and delete the column:
    OrmUtils.UpdateTable(Car.class).deleteColumn(“column_name”);
    

    Renaming itself in the form of an sql query raised some questions. First, I decided this, like deleting, by creating a new table with the desired field name, where the data from the old one is copied, and it is simply deleted, and the new one is renamed. But then, I came across this article and plan to try this method.

    Change field type

    Orm, again, can do everything for you, but you can help him:
    OrmUtils.UpdateTable(Car.class).changeColumnType("column_name");
    

    In principle, the second parameter could also pass a new type of column, but I did not want to give the programmer the opportunity to specify the wrong type, and then scold orm (:-)). The problem of the incompatibility of data of the old type and the new one is solved by the database itself, throwing an exception when copying the old table to the new one. But the column can also be easily reset by passing true as the second parameter :
    OrmUtils.UpdateTable(Car.class).changeColumnType(“column_name”, true);
    

    There was also a parameter in the design that indicates that the field should be cleared only in case of incompatibility of types, but has not yet been done.

    Conclusion


    These are the changes UCAOrm has undergone in the last two weeks. Not everything is laid out in github , since, as I wrote a little above, work on Updater is still underway, and not everything has been tested yet. There is also an idea to simplify the initial creation of tables a little: simply by calling the createByPackeg method of OrmUtils , passing in the name of the package in which orm will search for marked classes. But this is only an idea.
    As always, I will be glad to any new ideas and suggestions. Stay tuned for updates soon.

    Also popular now: