Skip to content
Andreas Ronge edited this page Feb 26, 2012 · 2 revisions

Migrations

  • By using migrations you can keep the code and the database in sync. There are two types of migrations : none lazy and lazy.
    In a none lazy migration the database is upgraded/downgraded all at once, while in lazy migrations the node/relationship is only upgraded/downgraded when the node or relationship is loaded.
  • A common usecase for migration is adding a lucene index on an existing database.

endprologue.

When do I need Migrations ?

The nice thing about a schema free database like neo4j is that you
normally don’t need migrations.
You need migrations if you already have a system in production and
want to change the structure of that database without deleting it.
So, if you don’t have a system in production you simply delete the
database from your local machine and add modify your Neo4j classes
(e.g by adding timestamp properties or a new has_n relationship).

Global Migrations

Here is an example of a use case for this feature.
Let say that we already have a database with nodes that have one property ‘name’.
Now we want to split that property into two properties: ‘surname’ and ‘given_name’.
We want to upgrade the database when it starts so we don’t use the lazy migration feature.
The neo4j database starts at version 0 by default.

Neo4j.migrate 1, "split name" do
  up 
   # find all people and change
   Person.all.each {|p|
     self.surname = self[:name].split[0]
     self.given_name = self[:name].split[1]
     self[:name] = nil
   end

    Neo4j.ref_node.outgoing(:foo).each { do stuff }
  end


  down do
   Person.all.each {|p|
     self.name = "#{self[:surname]} {self[:given_name]}"
     self[:surname] = nil
     self[:given_name] = nil
   end
    Neo4j.ref_node.outgoing(:foo).each { do stuff }
 end
end

If the code above has been loaded before the neo database starts it will automatically upgrade to version 1 (running all the migrations to the higest migration available).
You can force the neo4j to go to a specific version by using Neo4j#migrate! method.

MigrationMixin

It’s also possible to have one version for each node class.
In the example above there is only one version for the whole database.

Example

class Person < Neo4j::Rails::Model
  include Neo4j::Migrations::NodeMixin

  rule(:all)
end


Person.migration 1, :split_name do
  up do
    all.each_raw do |node|
      node[:given_name] = node[:name].split[0]
      node[:surname]    = node[:name].split[1]
      node[:name]       = nil
    end
  end

  down do
    all.each_raw do |node|
      node[:name]       = "#{node[:given_name]} #{node[:surname]}"
      node[:surename]   = nil
      node[:given_name] = nil
   end
end

In the example above we are using the all method which are generated by using the rule rule(:all).
The up and down method are evaulated in the context of the class (Person).
The all.each_raw method will return all node instances of type Person as Java nodes (node not wrapped in your Person class)

Add Index

You can add lucene indexes with the Neo4j::Migrations::NodeMixin.

Here is an example:

class Person
  index :name
end

Person.migration 42, :add_some_index do
   add_index :name
end

# add index on all previous nodes
Person.migrate!

When the migration is upgraded it will add the index :name.
Notice above that this index must be declared also in the Person class.
When downgrading the index will be removed.

Remove Index

You can also remove index just with the rm_index method in the migration.

Lazy Migration

The example above can also be run as lazy migration. i.e. perform the upgrade/downgrade when the node is loaded instead of all at once.
The following example demonstrates this feature:

class Person < Neo4j::Rails::Model
  include Neo4j::Migrations::LazyNodeMixin
end
  
Person.migration 1, :split_name do
  # Split name into two properties
  up do
    self[:name]
    self[:given_name] = self[:name].split[0]
    self[:surname]    = self[:name].split[1]
    self[:name]      = nil
  end

  down do
    self.name       = "#{self[:given_name]} #{self[:surname]}"
    self.surename   = nil
    self.given_name = nil
  end
end

Each node has a version property which is changed when an migration is executed.
The up and down blocks are evaluated in the context of the java node being loaded.

Rules and Functions

Migrations (Neo4j::Migrations::NodeMixin) can also be used to add rules/functions on already existing nodes.

Rules

Let say we have the following class and that we already have existing nodes in the database of class Person.

class Person
  include Neo4j::NodeMixin
end

Now we want to be able to find all instances of class Person. Since the Neo4j::NodeMixin does not include
the rule rule :all it is not possible by default to do that (unlike @Neo4j::Rails::Model which has this rule included).

The first step is adding the rule, open the class and add the rule :all and the MigrationMixin.

class Person < Neo4j::Rails::Model 
  include Neo4j::Migrations::NodeMixin
  rule :all
end

We now need to write an migration to upgrade all existing nodes.

Person.migration 1, "add rule :all" do
  up do
    Neo4j.all_nodes.each do |node|
      Person.trigger_rules(node)
    end
  end
end

# Now update all existing nodes
Person.migrate!

Functions

Let say that we now have run the “add rule :all” migration from above.
We now want to add the count function so that Neo4j.rb increase a property when a new node is created.
This is for example needed when doing paging. Without this function neo4j will traverse all nodes in order to count them
which could be very slow.

First we open the Person class again and add the count function.

class Person
  rule(:all, :functions => Neo4j::Functions::Count.new)
end

We now need to write a migration in order to upgrade all person nodes using this function.

Person.migration 2, "add count function" do
  up do
    func = Person.add_function_for(:all, Neo4j::Functions::Count)
    Person.all.each do |node|
      func.call(node)
    end
  end
end

This time there was no need to traverse every single node since we make use of the all function that we added in the migration 1 above.
We can now make use the the Count function:

# return the number of Person nodes without traversing and counting them.
Person.all.count

Running Migration in a New Thread

If you set the configuration property migration_thread to true all migration will be run in a new thread.
That means that you can still use the neo4j while the migration is taking place.

Transactions

By default all migrations is run inside an transaction.
If you instead want to create your own transaction you can disable it with auto_transaction

Example:

Person.migration 2, :foo do
  auto_transaction false

  up do  
   Neo4j::Transaction.run {...}
  end
  ..
end
Clone this wiki locally