Skip to content
Browse files

Adding rake task for migrations and fixing related issues with views

  • Loading branch information...
1 parent a132821 commit 0759b2af74316a7490edf35e7ac948ac316ce467 @samlown samlown committed Jun 12, 2012
View
12 lib/couchrest/model/designs/design.rb
@@ -89,6 +89,7 @@ def sync!(db = nil)
# end
#
def migrate(db = nil, &block)
+ db ||= database
doc = load_from_database(db)
cleanup = nil
@@ -98,11 +99,11 @@ def migrate(db = nil, &block)
db.save_doc(new_doc)
result = :created
- else doc['couchrest-hash'] != checksum
+ elsif doc['couchrest-hash'] != checksum
id = self['_id'] + "_migration"
# Delete current migration if there is one
- old_migration = db.get(id)
+ old_migration = load_from_database(db, id)
db.delete_doc(old_migration) if old_migration
# Save new design doc
@@ -130,7 +131,7 @@ def migrate(db = nil, &block)
view = new_doc['views'][name]
params = {:limit => 1}
params[:reduce] = false if view['reduce']
- db.view(name, params)
+ db.view("#{id}/_view/#{name}", params)
end
# Provide the result in block
@@ -208,8 +209,9 @@ def create_filter(name, function)
protected
- def load_from_database(db = database)
- db.get(self['_id'])
+ def load_from_database(db = database, id = nil)
+ id ||= self['_id']
+ db.get(id)
rescue RestClient::ResourceNotFound
nil
end
View
66 lib/couchrest/model/designs/view.rb
@@ -453,49 +453,45 @@ def define_and_create(design_doc, name, opts = {})
# is true, empty strings are permited in the indexes.
#
def define(design_doc, name, opts = {})
- # Don't create the map or reduce method if auto updates are disabled
- if design_doc.auto_update
- model = design_doc.model
- # Is this an all view?
- if name.to_s == 'all'
- opts[:map] = <<-EOF
- function(doc) {
- if (doc['#{model.model_type_key}'] == '#{model.to_s}') {
- emit(doc._id, null);
- }
+ model = design_doc.model
+
+ # Is this an all view?
+ if name.to_s == 'all'
+ opts[:map] = <<-EOF
+ function(doc) {
+ if (doc['#{model.model_type_key}'] == '#{model.to_s}') {
+ emit(doc._id, null);
}
- EOF
- elsif !opts[:map]
- if opts[:by].nil? && name.to_s =~ /^by_(.+)/
- opts[:by] = $1.split(/_and_/)
- end
-
- raise "View cannot be created without recognised name, :map or :by options" if opts[:by].nil?
-
- opts[:allow_blank] = opts[:allow_blank].nil? ? true : opts[:allow_blank]
- opts[:guards] ||= []
- opts[:guards].push "(doc['#{model.model_type_key}'] == '#{model.to_s}')"
-
- keys = opts[:by].map{|o| "doc['#{o}']"}
- emit = keys.length == 1 ? keys.first : "[#{keys.join(', ')}]"
- opts[:guards] += keys.map{|k| "(#{k} != null)"} unless opts[:allow_nil]
- opts[:guards] += keys.map{|k| "(#{k} != '')"} unless opts[:allow_blank]
- opts[:map] = <<-EOF
- function(doc) {
- if (#{opts[:guards].join(' && ')}) {
- emit(#{emit}, 1);
- }
+ }
+ EOF
+ elsif !opts[:map]
+ if opts[:by].nil? && name.to_s =~ /^by_(.+)/
+ opts[:by] = $1.split(/_and_/)
+ end
+ raise "View cannot be created without recognised name, :map or :by options" if opts[:by].nil?
+
+ opts[:allow_blank] = opts[:allow_blank].nil? ? true : opts[:allow_blank]
+ opts[:guards] ||= []
+ opts[:guards].push "(doc['#{model.model_type_key}'] == '#{model.to_s}')"
+
+ keys = opts[:by].map{|o| "doc['#{o}']"}
+ emit = keys.length == 1 ? keys.first : "[#{keys.join(', ')}]"
+ opts[:guards] += keys.map{|k| "(#{k} != null)"} unless opts[:allow_nil]
+ opts[:guards] += keys.map{|k| "(#{k} != '')"} unless opts[:allow_blank]
+ opts[:map] = <<-EOF
+ function(doc) {
+ if (#{opts[:guards].join(' && ')}) {
+ emit(#{emit}, 1);
}
- EOF
+ }
+ EOF
+ if opts[:reduce].nil?
opts[:reduce] = <<-EOF
function(key, values, rereduce) {
return sum(values);
}
EOF
end
- else
- # Assume there is always a map method
- opts[:map] ||= true
end
design_doc['views'] ||= {}
View
8 lib/couchrest/railtie.rb
@@ -18,6 +18,14 @@ def self.generator
Rails.application.class.to_s.underscore.gsub(/\/.*/, '')
end
end
+
+ config.before_configuration do
+ config.couchrest_model = CouchRest::Model::Base
+ end
+
+ rake_tasks do
+ Dir[File.join(File.dirname(__FILE__),'../tasks/*.rake')].each { |f| load f }
+ end
end
end
View
33 lib/tasks/migrations.rake
@@ -0,0 +1,33 @@
+
+namespace :couchrest do
+
+ desc "Migrate all the design docs found in each model"
+ task :migrate => :environment do
+
+ callbacks = [ ]
+ puts "Finding all CouchRest Models to migrate (excludes proxied models)"
+ CouchRest::Model::Base.subclasses.each do |model|
+ next unless model.respond_to?(:design_docs)
+ next if model.respond_to?(:model_proxy)
+ model.design_docs.each do |design|
+ print "Migrating #{model.to_s}##{design.method_name}... "
+ callback = design.migrate do |result|
+ puts "#{result.to_s.gsub(/_/, ' ')}"
+ end
+
+ # Is there a callback?
+ if callback
+ callbacks << {:design => design, :proc => callback}
+ end
+ end
+ end
+
+ callbacks.each do |cb|
+ puts "Copying design for #{cb[:design].model}##{cb[:design].method_name}"
+ cb[:proc].call
+ end
+
+ puts "Couchrest Model migrations finished"
+ end
+
+end
View
90 spec/unit/designs/design_spec.rb
@@ -8,7 +8,7 @@
reset_test_db!
end
- class DesignSampleModel < CouchRest::Model::Base
+ class DesignSampleModelBase < CouchRest::Model::Base
use_database DB
property :name
property :surname
@@ -20,6 +20,9 @@ class DesignSampleModel < CouchRest::Model::Base
end
end
+ class DesignSampleModel < DesignSampleModelBase
+ end
+
describe "class methods" do
before :all do
@@ -160,7 +163,86 @@ class DesignSampleModel < CouchRest::Model::Base
end
- describe "checksum" do
+ describe "#migrate" do
+ # WARNING! ORDER IS IMPORTANT!
+
+ describe "with limited changes" do
+
+ class DesignSampleModelMigrate < DesignSampleModelBase
+ end
+
+ before :all do
+ reset_test_db!
+ @mod = DesignSampleModelMigrate
+ @doc = @mod.design_doc
+ @db = @mod.database
+ end
+
+ it "should create new design if non exists" do
+ @db.should_receive(:view)
+ callback = @doc.migrate do |res|
+ res.should eql(:created)
+ end
+ doc = @db.get(@doc['_id'])
+ doc['views']['all'].should eql(@doc['views']['all'])
+ callback.should be_nil
+ end
+
+ it "should not change anything if design is up to date" do
+ @doc.sync
+ @db.should_not_receive(:view)
+ callback = @doc.migrate do |res|
+ res.should eql(:no_change)
+ end
+ callback.should be_nil
+ end
+
+ end
+
+ describe "migrating a document if there are changes" do
+
+ class DesignSampleModelMigrate2 < DesignSampleModelBase
+ end
+
+ before :all do
+ reset_test_db!
+ @mod = DesignSampleModelMigrate2
+ @doc = @mod.design_doc
+ @db = @mod.database
+ @doc.sync!
+ @doc.create_view(:by_name_and_surname)
+ @doc_id = @doc['_id'] + '_migration'
+ end
+
+ it "should save new migration design doc" do
+ @db.should_receive(:view).with("#{@doc_id}/_view/by_name", {:limit => 1, :reduce => false})
+ @callback = @doc.migrate do |res|
+ res.should eql(:migrated)
+ end
+ @callback.should_not be_nil
+
+ # should not have updated original view until cleanup
+ doc = @db.get(@doc['_id'])
+ doc['views'].should_not have_key('by_name_and_surname')
+
+ # Should have created the migration
+ new_doc = @db.get(@doc_id)
+ new_doc.should_not be_nil
+
+ # should be possible to perform cleanup
+ @callback.call
+ lambda { new_doc = @db.get(@doc_id) }.should raise_error RestClient::ResourceNotFound
+
+ doc = @db.get(@doc['_id'])
+ doc['views'].should have_key('by_name_and_surname')
+ end
+
+ end
+
+ end
+
+
+ describe "#checksum" do
before :all do
@mod = DesignSampleModel
@@ -271,12 +353,12 @@ class DesignSampleModel < CouchRest::Model::Base
it "should calculate a consistent checksum for model" do
#WithTemplateAndUniqueID.design_doc.checksum.should eql('caa2b4c27abb82b4e37421de76d96ffc')
- WithTemplateAndUniqueID.design_doc.checksum.should eql('f0973aaa72e4db0efeb2a281ea297cec')
+ WithTemplateAndUniqueID.design_doc.checksum.should eql('7f44e88afbce06204010c49b76f31bcf')
end
it "should calculate checksum for complex model" do
#Article.design_doc.checksum.should eql('70dff8caea143bf40fad09adf0701104')
- Article.design_doc.checksum.should eql('7ef39bffdf5837e8b078411ac417d860')
+ Article.design_doc.checksum.should eql('0f25a2d9f86e31ebd61b29863a41d5ed')
end
it "should cache the generated checksum value" do
View
29 spec/unit/designs/view_spec.rb
@@ -100,39 +100,24 @@ class DesignViewModel < CouchRest::Model::Base
describe ".define" do
- describe "with no auto update" do
- before :each do
- @design_doc = { }
- @design_doc.stub!(:model).and_return(DesignViewModel)
- @design_doc.stub!(:auto_update).and_return(false)
- end
-
- it "should set map view to true" do
- @klass.define(@design_doc, 'test_view')
- @design_doc['views']['test_view']['map'].should eql(true)
- @design_doc['views']['test_view']['reduce'].should be_false
- end
-
- it "should set reduce to true if set" do
- @klass.define(@design_doc, 'test_view', :reduce => true)
- @design_doc['views']['test_view']['map'].should eql(true)
- @design_doc['views']['test_view']['reduce'].should eql(true)
- end
- end
-
- describe "with auto update" do
+ describe "under normal circumstances" do
before :each do
@design_doc = { }
@design_doc.stub!(:model).and_return(DesignViewModel)
- @design_doc.stub!(:auto_update).and_return(true)
end
it "should add a basic view" do
@klass.define(@design_doc, 'test_view', :map => 'foo')
@design_doc['views']['test_view'].should_not be_nil
end
+ it "should not overwrite reduce if set" do
+ @klass.define(@design_doc, 'by_title', :reduce => true)
+ @design_doc['views']['by_title']['map'].should_not be_blank
+ @design_doc['views']['by_title']['reduce'].should eql(true)
+ end
+
it "should auto generate mapping from name" do
lambda { @klass.define(@design_doc, 'by_title') }.should_not raise_error
str = @design_doc['views']['by_title']['map']

0 comments on commit 0759b2a

Please sign in to comment.
Something went wrong with that request. Please try again.