Skip to content

Commit

Permalink
Added minimal support for data migrations.
Browse files Browse the repository at this point in the history
  • Loading branch information
paulcarey committed May 27, 2009
1 parent 0d69818 commit d398ea0
Show file tree
Hide file tree
Showing 12 changed files with 240 additions and 12 deletions.
16 changes: 16 additions & 0 deletions README.textile
@@ -1,5 +1,7 @@
h3. What's New?

* 2009-05-27
** Added minimal support for data migrations. Although CouchDB's nature removes the necessity for migrations, certain knowledge that all objects possess a particular property can simplify client logic. This desire for simplification is the rationale behind this change.
* 2009-04-19
** Defaults to taf2-curb, falling back to Net/HTTP if it taf2-curb can't be loaded. Thanks to "Fred Cheung":http://www.spacevatican.org/2009/4/13/fun-with-ruby-http-clients.
** For those interested in using RelaxDB with an ETag based cache, "look here":http://github.com/fcheung/relaxdb/commit/1d9acfd5f6b3c23da0d275252b6a6e064865440e
Expand Down Expand Up @@ -171,6 +173,20 @@ h3. Creating views by hand
</code>
</pre>

h3. Migrations

<pre>
<code>
$ cat 001_double.rb
RelaxDB::Migration.run Primitives do |p|
p.num *= 2
p
end

$ ruby -e 'RelaxDB::Migration.run_all Dir["./*.rb"]'
</code>
</pre>

h3. Visualise

"Fuschia":http://github.com/paulcarey/fuschia/tree/master offers a web front end for visualising inter-document relationships.
Expand Down
2 changes: 1 addition & 1 deletion Rakefile
Expand Up @@ -4,7 +4,7 @@ require 'spec/rake/spectask'

PLUGIN = "relaxdb"
NAME = "relaxdb"
GEM_VERSION = "0.3.2"
GEM_VERSION = "0.3.3"
AUTHOR = "Paul Carey"
EMAIL = "paul.p.carey@gmail.com"
HOMEPAGE = "http://github.com/paulcarey/relaxdb/"
Expand Down
3 changes: 3 additions & 0 deletions lib/relaxdb.rb
Expand Up @@ -30,6 +30,7 @@
require 'relaxdb/extlib'
require 'relaxdb/has_many_proxy'
require 'relaxdb/has_one_proxy'
require 'relaxdb/migration'
require 'relaxdb/paginate_params'
require 'relaxdb/paginator'
require 'relaxdb/query'
Expand All @@ -43,5 +44,7 @@
require 'relaxdb/views'
require 'more/grapher.rb'

require 'relaxdb/migration_version'

module RelaxDB
end
40 changes: 40 additions & 0 deletions lib/relaxdb/migration.rb
@@ -0,0 +1,40 @@
module RelaxDB

class Migration

def self.run klass, limit = 1000
query = lambda do |page_params|
RelaxDB.paginate_view "#{klass}_all", :startkey => nil, :endkey => {}, :attributes => [:_id],
:page_params => page_params, :limit => limit
end

objs = query.call({})
until objs.empty?
migrated = objs.map { |o| yield o }.flatten.reject { |o| o.nil? }
RelaxDB.bulk_save! *migrated
objs = objs.next_params ? query.call(objs.next_params) : []
end
end

#
# Runs all outstanding migrations in a given directory
#
# ==== Example
# RelaxDB::Migration.run_all Dir["couchdb/migrations/**/*.rb"]
#
def self.run_all file_names, action = lambda { |fn| require fn }
v = RelaxDB::MigrationVersion.version
file_names.select { |fn| fv(fn) > v }.each do |fn|
RelaxDB.logger.info "Applying #{fn}"
action.call fn
RelaxDB::MigrationVersion.update fv(fn)
end
end

def self.fv file_name
File.basename(file_name).split("_")[0].to_i
end

end

end
21 changes: 21 additions & 0 deletions lib/relaxdb/migration_version.rb
@@ -0,0 +1,21 @@
class RelaxDB::MigrationVersion < RelaxDB::Document

DOC_ID = "relaxdb_migration_version"

property :version, :default => 0

def self.version
retrieve.version
end

def self.update v
mv = retrieve
mv.version = v
mv.save!
end

def self.retrieve
(v = RelaxDB.load(DOC_ID)) ? v : new(:_id => DOC_ID).save!
end

end
2 changes: 1 addition & 1 deletion lib/relaxdb/relaxdb.rb
Expand Up @@ -73,7 +73,7 @@ def bulk_save!(*objs)
end

pre_save_success = objs.inject(true) { |s, o| s &= o.pre_save }
raise ValidationFailure, objs unless pre_save_success
raise ValidationFailure, objs.inspect unless pre_save_success

docs = {}
objs.each { |o| docs[o._id] = o }
Expand Down
2 changes: 1 addition & 1 deletion lib/relaxdb/views.rb
Expand Up @@ -8,7 +8,7 @@ def self.all(kls)
function(doc) {
var class_match = #{kls_check kls}
if (class_match) {
emit(null, doc);
emit(doc._id, doc);
}
}
QUERY
Expand Down
21 changes: 17 additions & 4 deletions relaxdb.gemspec
@@ -1,7 +1,10 @@
# Updating the gemspec
# ruby -e 'Dir["spec/*"].each { |fn| puts "\"#{fn}\," }'

Gem::Specification.new do |s|
s.name = "relaxdb"
s.version = "0.3.2"
s.date = "2009-05-22"
s.version = "0.3.3"
s.date = "2009-05-27"
s.summary = "RelaxDB provides a simple interface to CouchDB"
s.email = "paul.p.carey@gmail.com"
s.homepage = "http://github.com/paulcarey/relaxdb/"
Expand All @@ -21,6 +24,8 @@ Gem::Specification.new do |s|
"lib/relaxdb/has_many_proxy.rb",
"lib/relaxdb/has_one_proxy.rb",
"lib/relaxdb/net_http_server.rb",
"lib/relaxdb/migration.rb",
"lib/relaxdb/migration_version.rb",
"lib/relaxdb/paginate_params.rb",
"lib/relaxdb/paginator.rb",
"lib/relaxdb/query.rb",
Expand All @@ -39,18 +44,26 @@ Gem::Specification.new do |s|
"lib/more/atomic_bulk_save_support.rb",
"spec/belongs_to_spec.rb",
"spec/callbacks_spec.rb",
"spec/design_doc_spec.rb",
"spec/derived_properties_spec.rb",
"spec/design_doc_spec.rb",
"spec/doc_inheritable_spec.rb",
"spec/document_spec.rb",
"spec/has_many_spec.rb",
"spec/has_one_spec.rb",
"spec/migration_spec.rb",
"spec/migration_version_spec.rb",
"spec/paginate_params_spec.rb",
"spec/paginate_spec.rb",
"spec/query_spec.rb",
"spec/references_many_spec.rb",
"spec/relaxdb_spec.rb",
"spec/server_spec.rb",
"spec/spec.opts",
"spec/spec_helper.rb",
"spec/spec_models.rb",
"spec/view_object_spec.rb"]
"spec/view_by_spec.rb",
"spec/view_object_spec.rb",
"spec/view_spec.rb"]
s.bindir = "bin"
s.autorequire = "relaxdb"
s.add_dependency "extlib", ">= 0.9.4" # removed ", runtime" as was failing locally
Expand Down
97 changes: 97 additions & 0 deletions spec/migration_spec.rb
@@ -0,0 +1,97 @@
require File.join(File.dirname(__FILE__), "spec_helper")
require File.join(File.dirname(__FILE__), "spec_models")

describe RelaxDB::Migration do

before(:each) do
@mig = RelaxDB::Migration
setup_test_db
end

it "should yield each obj to the block and save the result" do
Primitives.new(:num => 5).save!
r = @mig.run Primitives do |p|
pn = Primitives.new(:num => p.num * 2)
[p, pn]
end
Primitives.by_num.map { |p| p.num}.should == [5, 10]
end

it "should raise an exception if a save results in a conflict" do
op = Primitives.new.save!
lambda do
@mig.run Primitives do |p|
op.save!
p
end
end.should raise_error(RelaxDB::UpdateConflict)
end

it "should not save docs for blocks that return nil" do
Primitives.new.save!
@mig.run Primitives do |p|
nil
end
end

describe "multiple docs" do

before(:each) do
ps = (1..5).map { |i| Primitives.new :num => i }
RelaxDB.bulk_save *ps
RelaxDB.db.reset_req_count
end

# Note: two extra requests per paginate_view request are required
it "should operate on a doc set of the given size aka limit" do
@mig.run(Primitives, 1) { |p| p.num *= p.num; p }
RelaxDB.db.req_count.should == 10 + 5 * 2
Primitives.by_num.map { |p| p.num }.should == [1, 4, 9, 16, 25]
end

it "should operate on a doc set of default size" do
@mig.run(Primitives) { |p| p.num *= p.num; p }
RelaxDB.db.req_count.should == 2 + 1 * 2
Primitives.by_num.map { |p| p.num }.should == [1, 4, 9, 16, 25]
end

end

describe "#fv" do
it "should return valid numbers" do
@mig.fv("foo/bar/001_foo.rb").should == 1
end
end

describe ".run_all" do

it "should save the version after each successful migration" do
@mig.run_all "a/b/1_", lambda {}
RelaxDB::MigrationVersion.version.should == 1
end

it "should not run those migrations whose version is less than the current version" do
v = RelaxDB::MigrationVersion.retrieve
v.version = 2
v.save!
@mig.run_all "3_", lambda {}
RelaxDB::MigrationVersion.version.should == 3
end

it "should run those migrations whose version is greater than the current version" do
v = RelaxDB::MigrationVersion.retrieve
v.version = 2
v.save!
@mig.run_all "1_foo", lambda {}
RelaxDB::MigrationVersion.version.should == 2
end

it "should raise an exception on failure" do
lambda do
@mig.run_all "1_foo", lambda { raise "Expected" }
end.should raise_error(RuntimeError, "Expected")
end

end

end
28 changes: 28 additions & 0 deletions spec/migration_version_spec.rb
@@ -0,0 +1,28 @@
require File.join(File.dirname(__FILE__), "spec_helper")
require File.join(File.dirname(__FILE__), "spec_models")

describe RelaxDB::MigrationVersion do

before(:each) do
setup_test_db
end

it "should not exist in a clean db" do
RelaxDB.load(RelaxDB::MigrationVersion::DOC_ID).should be_nil
end

it "should return zero when it doesnt exist" do
RelaxDB::MigrationVersion.version.should == 0
end

it "should autosave on retrieval when when it doesnt exist" do
RelaxDB::MigrationVersion.version
RelaxDB.load(RelaxDB::MigrationVersion::DOC_ID).should be
end

it "should return the saved version" do
RelaxDB::MigrationVersion.update 10
RelaxDB::MigrationVersion.version.should == 10
end

end
16 changes: 11 additions & 5 deletions spec/relaxdb_spec.rb
Expand Up @@ -39,11 +39,7 @@
ta.should == t1
tb.should == t2
end

it "should succeed when passed no args" do
RelaxDB.bulk_save
end


it "should return false on failure" do
c = Class.new(RelaxDB::Document) do
property :foo, :validator => lambda { false }
Expand Down Expand Up @@ -128,6 +124,16 @@

describe ".bulk_save!" do

it "should succeed when passed no args" do
RelaxDB.bulk_save!
end

it "should raise when passed a nil value" do
lambda do
RelaxDB.bulk_save! *[nil]
end.should raise_error
end

it "should raise an exception if a obj fails validation" do
c = Class.new(RelaxDB::Document) do
property :foo, :validator => lambda { false }
Expand Down
4 changes: 4 additions & 0 deletions spec/spec_helper.rb
Expand Up @@ -9,8 +9,12 @@
$:.unshift(File.dirname(__FILE__) + '/../lib')
require 'relaxdb'

class RdbFormatter; def call(sv, time, progname, msg); puts msg; end; end

def setup_test_db
# RelaxDB.configure :host => "localhost", :port => 5984, :design_doc => "spec_doc", :logger => Logger.new(STDOUT)
# RelaxDB.logger.formatter = RdbFormatter.new

RelaxDB.configure :host => "localhost", :port => 5984, :design_doc => "spec_doc"

RelaxDB.delete_db "relaxdb_spec" rescue "ok"
Expand Down

0 comments on commit d398ea0

Please sign in to comment.