Perform Migration by Adding List() and Another Model Class

Perform migration by adding List() and another model class

Edited After Receiving Clarification

Alrighty. So since you do want to pre-populate areas when you add it to your model, you will need to implement some logic in your migration block after all.

let migrationBlock: MigrationBlock = { migration, oldSchemaVersion in
migration.enumerate(Region.className()) { oldObject, newObject in
if oldSchemaVersion < 1 {
let areas = newObject?["areas"] as? List<MigrationObject>
// Add new objects to 'areas' as needed
}
}
}

There's some sample code showing how to handle List objects in migrations in Realm Swift's sample code collection

If your goal in adding a region property to Area is so you can find out which Region object this Area is a child of, then you don't need to implement that as a model property. Instead, you can use linkingObjects(_: forProperty: ) to have Realm work that out on your behalf.

class Area: Object {
dynamic var id = 0
dynamic var name = ""
var regions: [Region] {
return linkingObjects(Region.self, forProperty: "areas")
}

override static func primaryKey() -> String? {
return "id"
}
}

To confirm what I said in the comments, migrations are a one-way path. They cannot be downgraded to previous schema versions. If you want to rapidly debug the migration process on a Realm file, I recommend putting the original Realm file aside and working on copies.


Original Answer

Do you actually have any data you wish to add to these new properties? Since it doesn't look like you do, you shouldn't need to implement any code in the migration block.

Simply increase the Realm schema version number, and supply an empty migration block.

let config = Realm.Configuration(
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in

})

Realm.Configuration.defaultConfiguration = config

While the migration block cannot be nil, you only need to put code in there if there's any data in an old Realm file that you want to manipulate during a migration (i.e., moving it to another property). If you're adding brand new properties, it's not necessary to do anything to them inside the migration block.

It takes a little while to get into the mindset of Realm migrations, but thankfully once you do, you realise they're easier than you thought. :)

(Disclaimer: I work for Realm, but I use it in one of my own shipping iOS apps, where I've played with multiple migrations on real user data at this point. :) )

Perform Realm migration from one List to another updating property value

Realm migration blocks (and their dynamic API) aren't really well-suited for your particular use case. Neither index(of:) nor append() can be used properly with dynamic objects.

My recommendation for approaching this problem is to simply set the quantity properties to 1 in the migration block as you are doing, and then set a boolean flag that indicates that you need to perform the deck update. Then, before you do anything else (perhaps in application(_: didFinishLaunchingWithOptions:)), open the Realm and check for that flag. If that flag is set you can then open the Realm and perform the migration using the normal Realm API.

Here is some example code:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Get the configuration and set the migration block on it
var config = Realm.Configuration.defaultConfiguration
config.schemaVersion = 2
var needsMigrationToV2 = false

config.migrationBlock = { migration, oldSchemaVersion in
if oldSchemaVersion < 2 {
migration.enumerateObjects(ofType: CardDTO.className()) { oldObject, newObject in
newObject!["quantity"] = 1
}
needsMigrationToV2 = true
}
}
let realm = try! Realm(configuration: config)
// Run the rest of the migration using the typed API
if needsMigrationToV2 {
let allDecks = realm.objects(DeckDTO.self)
try! realm.write {
for deck in allDecks {
var uniqueCards : [CardDTO] = []
for card in deck.list {
if uniqueCards.contains(card) {
card.quantity += 1
} else {
uniqueCards.append(card)
}
}
deck.list.removeAll()
deck.list.append(objectsIn: uniqueCards)
}
}
}
return true
}

One more thing to note is that List<T> properties should be declared as let, not var, since reassigning to a List<T> property is an error.

How to organize migration for two related models and automatically set default field value for id of newly created object?

I left my previous answer just to show search for thoughts. Finally I've founded fully automatic solution, so it's not necessary anymore to manually edit django generated migrations, but the price is monkey patching, as often.

The idea is to provide callable for default of ForeignKey, which creates default instance of referenced model, if it is not exists. But the problem is, that this callable can be called not only in final Django project stage, but also during migrations, with old project stages, so it can be called for deleted model on early stages, when the model was still existing.

The standard solution in RunPython operations is to use apps registry from the migration state, but this feature unavailable for our callable, cause this registry is provided as argument for RunPython and not available globally. But to support all scenarios of migration applying and rollback we need to detect are we in migration or not, and access appropriate apps registry.

The only solution is to monkey patch AddField and RemoveField operations to keep migration apps registry in global variable, if we are in migration.

migration_apps = None

def set_migration_apps(apps):
global migration_apps
migration_apps = apps

def get_or_create_default(model_name, app_name):
M = (migration_apps or django.apps.apps).get_model(app_name, model_name)

try:
return M.objects.get(isDefault=True).id

except M.DoesNotExist as e:
o = M.objects.create(isDefault=True)
print '{}.{} default object not found, creating default object : OK'.format(model_name, app_name)
return o

def monkey_patch_fields_operations():
def patch(klass):

old_database_forwards = klass.database_forwards
def database_forwards(self, app_label, schema_editor, from_state, to_state):
set_migration_apps(to_state.apps)
old_database_forwards(self, app_label, schema_editor, from_state, to_state)
klass.database_forwards = database_forwards

old_database_backwards = klass.database_backwards
def database_backwards(self, app_label, schema_editor, from_state, to_state):
set_migration_apps(to_state.apps)
old_database_backwards(self, app_label, schema_editor, from_state, to_state)
klass.database_backwards = database_backwards

patch(django.db.migrations.AddField)
patch(django.db.migrations.RemoveField)

The rest, including Defaultable model with data integrity check are in GitHub repository

How do I add another field to my model without getting an error?

According to this answer you have to make sure you:

  1. Add your app to INSTALLED_APPS inside settings.py, otherwise makemigrations will not check for changes in your models
  2. Run ./manage.py makemigrations <app_name> if your app doesn't have a migrations module yet (i.e. it's going to be your app's 0001_initial.py migration)

If you can cross these two requirements off your list (i.e. you're doing subsequent changes to your models), then ./manage.py makemigrations without any <app_name> should simply work.

How to modify a models who's already migrated in Database?

To elaborate on my comment above...

Adding a new non-nullable ForeignKey in Django is generally a three-step process.

  1. First, you add the new ForeignKey to your model definition with null=True, and run makemigrations. This will create a migration that will add the field, nothing special about it. Executing this migration will add a column with all rows having NULL as the value.
  2. Second, you create a new empty migration for the same app (makemigrations --empty), then edit that migration to contain a data migration step. This is where you'll need to, according to your business logic, choose some value for the new foreign key.
  3. Third, you modify the ForeignKey in your model definition to set null=False and create a third migration with makemigrations. Django will ask whether you've dealt with nulls somehow – you need to say that "yep, I swear I have" (since you did, above in step 2).

In practice, for a simplified version of OP's question where we'll want to add an User foreign key:

Original state

class Post(models.Model):
name = models.CharField(max_length=100)

1a. Add nullable field.

class Post(models.Model):
name = models.CharField(max_length=100)
author = models.ForeignKey(User, null=True, on_delete=models.CASCADE))

1b. Run makemigrations.

$ python manage.py makemigrations
Migrations for 'something':
something/migrations/0002_post_author.py
- Add field author to post

2a. Create a new empty migration.

$ python manage.py makemigrations something --empty -n assign_author
Migrations for 'something':
something/migrations/0003_assign_author.py

2b. Edit the migration file.

More information on data migrations can be found, as always, in the manual.

from django.db import migrations

def assign_author(apps, schema_editor):
User = apps.get_model('auth', 'User') # or whatever is your User model
Post = apps.get_model('something', 'Post') # or wherever your Post model is
user = User.objects.filter(is_superuser=True).first() # Choose some user...
assert user # ... and ensure it exists...
Post.objects.all().update(author=user) # and bulk update all posts.

class Migration(migrations.Migration):

dependencies = [...]

operations = [
migrations.RunPython(assign_author, migrations.RunPython.noop),
]

3a. Make the field non-nullable.

class Post(models.Model):
name = models.CharField(max_length=100)
author = models.ForeignKey(User, null=False, on_delete=models.CASCADE))

3b. Run Makemigrations.

Answer truthfully to the question – you've just added a RunPython operation.

$ python manage.py makemigrations something -n post_author_non_null
You are trying to change the nullable field 'author' on something. to non-nullable without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Ignore for now, and let me handle existing rows with NULL myself (e.g. because you added a RunPython or RunSQL operation to handle NULL values in a previous data migration)
3) Quit, and let me add a default in models.py
Select an option: 2
Migrations for 'something':
something/migrations/0004_post_author_non_null.py
- Alter field author on post

All done!

Running migrate will now run these three migrations and your model will have author without data loss.

adding a list of objects (of a class written by me) to a existing class

I generated a migration file using the command rails generate migration CreatePersonsSubjects. The names are put together in alphabetical order (convention).
The result of this command is an skeleton in which code can be entered to change database tables.
This went to the "up" part:

create_table :persons_subjects, :id => false do |t|
t.integer :person_id, :null => false
t.integer :subject_id, :null => false
end

In the "down" part these actions can be reverted:

   drop_table :persons_subjects

Finally I added a field in the person class (and only to the person class, by the way. But one could/should also add a similar line to the subject). The name is convention too.

  has_and_belongs_to_many :subjects

The subjects can be used for example like this in the view:

<div>Courses taken:
<ul>
<% @person.subjects.each do |subject| %>
<li><%= link_to subject.name, subject, :class => 'action' %></li>
<% end %>
</ul>
</div>

Realm Migration: Migrating objects to another

You don't need to do anything with LinkingObjects, realm calculates those automatically when you query them.

All you'll need to do in your migration is set media to be a new Media object with the values you already have.

Other notes:

  • The second enumerateObjects isn't needed.
  • You can remove image, coverImage, and video from Item since you're moving those value to Media

Edit: This is what you would need to have in your migration.

let media = Media()
media.fullImage = oldItem?["image"] as! String
media.thumbnailImage = oldItem?["coverImage"] as! String
media.video = oldItem?["video"] as! String

newItem?["media"] = media

How to add new field in model after custom migration which access model before new field?

You should import the historical model, not your current model. Django keeps track how the model looks like before you run that migration, you can access such model with the apps.get_model(…) method [Django-doc]:

from django.db import migrations
# No import from myapp.models!

def create_user(apps, schema_editor):
MyUser = apps.get_model('my_user', 'MyUser')
user = MyUser.objects.get_or_create(id=123)
user.address = 'demo'
user.save()

def delete_user(apps, schema_editor):
MyUser = apps.get_model('my_user', 'MyUser')
MyUser.objects.filter(id=123).delete()

class Migration(migrations.Migration):

dependencies = [
('my_user', '001_initial'),
]

The MyUser model that we thus here use will not per se have all fields of the one you define in models.py, nor will the fields per se have the same type. You thus can here work with the model like it was at that point in time, and thus define data migrations. The records will than later be updated if you made an extra field for example.



Related Topics



Leave a reply



Submit