caster 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/cast +30 -0
- data/lib/caster.rb +39 -0
- data/lib/caster/execution.rb +75 -0
- data/lib/caster/migration.rb +39 -0
- data/lib/caster/migrator.rb +72 -0
- data/lib/caster/operation.rb +19 -0
- data/lib/caster/ref/reference.rb +45 -0
- data/lib/caster/transform/add.rb +20 -0
- data/lib/caster/transform/clone.rb +13 -0
- data/lib/caster/transform/create.rb +18 -0
- data/lib/caster/transform/delete.rb +8 -0
- data/lib/caster/transform/remove.rb +13 -0
- data/lib/caster/transform/rename.rb +15 -0
- metadata +104 -0
data/bin/cast
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'caster'
|
4
|
+
require 'caster/migrator'
|
5
|
+
require 'thor'
|
6
|
+
require 'couchrest'
|
7
|
+
|
8
|
+
class Cast < Thor
|
9
|
+
|
10
|
+
desc 'version', 'prints the current version of your database'
|
11
|
+
method_option :db, :aliases => '-d', :required => true
|
12
|
+
def version
|
13
|
+
db = CouchRest.database! "http://#{Caster.config[:host]}:#{Caster.config[:port]}/#{options[:db]}"
|
14
|
+
begin
|
15
|
+
puts db.get("#{Caster.config[:metadoc_id_prefix]}_#{database}")['version']
|
16
|
+
rescue
|
17
|
+
puts "No version information available."
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'up', 'executes migrations defined in cast files in the current directory'
|
22
|
+
method_option :db, :aliases => '-d', :required => true
|
23
|
+
method_option :version, :aliases => '-v'
|
24
|
+
def up path = '.'
|
25
|
+
Migrator.new.migrate_dir options[:db], path, options[:version]
|
26
|
+
puts 'Done.'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
Cast.start
|
data/lib/caster.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'caster/migration'
|
3
|
+
|
4
|
+
include Caster
|
5
|
+
|
6
|
+
module Caster
|
7
|
+
|
8
|
+
@config = {
|
9
|
+
:host => '127.0.0.1',
|
10
|
+
:port => '5984',
|
11
|
+
:metadoc_type => 'caster_metadoc',
|
12
|
+
:metadoc_id_prefix => 'caster'
|
13
|
+
}
|
14
|
+
|
15
|
+
@valid_config_keys = @config.keys
|
16
|
+
|
17
|
+
def self.configure opts = {}
|
18
|
+
opts.each do |k, v|
|
19
|
+
@config[k.to_sym] = v if @valid_config_keys.include? k.to_sym
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.configure_with path_to_yaml_file
|
24
|
+
begin
|
25
|
+
config = YAML.load_file path_to_yaml_file
|
26
|
+
rescue Errno::ENOENT
|
27
|
+
log :warning, "YAML configuration file couldn't be found.. using defaults."
|
28
|
+
return
|
29
|
+
rescue Psych::SyntaxError
|
30
|
+
log :warning, "YAML configuration file contains invalid syntax.. using defaults."
|
31
|
+
return
|
32
|
+
end
|
33
|
+
configure(config)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.config
|
37
|
+
@config
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'couchrest'
|
2
|
+
require 'caster/operation'
|
3
|
+
require 'caster/transform/add'
|
4
|
+
require 'caster/transform/remove'
|
5
|
+
require 'caster/transform/rename'
|
6
|
+
require 'caster/transform/create'
|
7
|
+
require 'caster/transform/delete'
|
8
|
+
require 'caster/transform/clone'
|
9
|
+
require 'caster/ref/reference'
|
10
|
+
|
11
|
+
module Caster
|
12
|
+
|
13
|
+
# defines an execution scope which is a set of documents over which migration operations run
|
14
|
+
class Execution
|
15
|
+
|
16
|
+
def initialize db, view, query, &block
|
17
|
+
@db = db
|
18
|
+
@view = view
|
19
|
+
@query = query
|
20
|
+
@block = block
|
21
|
+
end
|
22
|
+
|
23
|
+
def add field, value
|
24
|
+
@operations << Operation.new(@db, Add.new(field, value))
|
25
|
+
end
|
26
|
+
alias_method :update, :add
|
27
|
+
|
28
|
+
def remove field
|
29
|
+
@operations << Operation.new(@db, Remove.new(field))
|
30
|
+
end
|
31
|
+
|
32
|
+
def rename old_name, new_name
|
33
|
+
@operations << Operation.new(@db, Rename.new(old_name, new_name))
|
34
|
+
end
|
35
|
+
|
36
|
+
def create params
|
37
|
+
@operations << Operation.new(@db, Create.new(params))
|
38
|
+
end
|
39
|
+
|
40
|
+
def delete
|
41
|
+
@operations << Operation.new(@db, Delete.new)
|
42
|
+
end
|
43
|
+
|
44
|
+
def create_on db_handle, params
|
45
|
+
@operations << Operation.new(db_handle, Clone.new(params))
|
46
|
+
end
|
47
|
+
|
48
|
+
def from scope, query = {}
|
49
|
+
if scope.scan('/').length == 1
|
50
|
+
return Reference.new @db, scope, query
|
51
|
+
else
|
52
|
+
database_name, view = scope.split('/', 2)
|
53
|
+
db = CouchRest.database "http://#{Caster.config[:host]}:#{Caster.config[:port]}/#{database_name}"
|
54
|
+
return Reference.new db, view, query
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def execute
|
59
|
+
rdocs = @db.view(@view, @query)['rows']
|
60
|
+
db_docs_map = Hash.new { |k, v| k[v] = [] }
|
61
|
+
rdocs.each do |rdoc|
|
62
|
+
doc = rdoc.has_key?('doc')? rdoc['doc'] : rdoc['value']
|
63
|
+
|
64
|
+
@operations = []
|
65
|
+
instance_exec doc.clone, &@block
|
66
|
+
@operations.each do |op|
|
67
|
+
db_docs_map[op.db_handle] << op.transformation.execute(doc)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
db_docs_map.each do |db, docs|
|
71
|
+
db.bulk_save docs
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'caster/execution'
|
2
|
+
|
3
|
+
module Caster
|
4
|
+
|
5
|
+
def migrate database_name, &block
|
6
|
+
@db = CouchRest.database "http://#{Caster.config[:host]}:#{Caster.config[:port]}/#{database_name}"
|
7
|
+
yield
|
8
|
+
@db = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def migrate_script database_name, code
|
12
|
+
migrate database_name do
|
13
|
+
self.instance_eval code, __FILE__, __LINE__
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def over scope, query = {}, &block
|
18
|
+
database_name, view = split(scope)
|
19
|
+
db = CouchRest.database "http://#{Caster.config[:host]}:#{Caster.config[:port]}/#{database_name}" if @db == nil
|
20
|
+
Execution.new(db || @db, view, query, &block).execute
|
21
|
+
end
|
22
|
+
|
23
|
+
def split scope
|
24
|
+
if scope.count('/') == 1
|
25
|
+
return nil, scope
|
26
|
+
elsif scope.count('/') == 2
|
27
|
+
return scope.split('/', 2)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# fall down to couchrest methods
|
32
|
+
def method_missing method_name, *args
|
33
|
+
if @db.respond_to? method_name
|
34
|
+
@db.send method_name, *args
|
35
|
+
else
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'caster/migration'
|
2
|
+
require 'couchrest'
|
3
|
+
|
4
|
+
module Caster
|
5
|
+
|
6
|
+
class Migrator
|
7
|
+
|
8
|
+
def migrate_dir database, path, max_version = nil
|
9
|
+
db = CouchRest.database! "http://#{Caster.config[:host]}:#{Caster.config[:port]}/#{database}"
|
10
|
+
|
11
|
+
current_version = nil
|
12
|
+
begin
|
13
|
+
current_version = db.get("#{Caster.config[:metadoc_id_prefix]}_#{database}")['version']
|
14
|
+
rescue
|
15
|
+
# ignored
|
16
|
+
end
|
17
|
+
|
18
|
+
path = path.sub /(\/)+$/, ''
|
19
|
+
|
20
|
+
migrations = Dir["#{path}/*.cast"].map do |file|
|
21
|
+
version, db_in_filename = File.basename(file, '.cast').split '.'
|
22
|
+
{ :version => version , :database => db_in_filename, :filepath => file }
|
23
|
+
end
|
24
|
+
|
25
|
+
db_filtered = migrations.map do |migration|
|
26
|
+
migration if database == migration[:database]
|
27
|
+
end.compact
|
28
|
+
|
29
|
+
min_version_filtered = db_filtered.map do |migration|
|
30
|
+
migration if current_version == nil or migration[:version] > current_version
|
31
|
+
end.compact
|
32
|
+
|
33
|
+
max_version_filtered = min_version_filtered.map do |migration|
|
34
|
+
migration if max_version == nil or migration[:version] <= max_version
|
35
|
+
end.compact
|
36
|
+
|
37
|
+
filtered_and_sorted_files = max_version_filtered.map do |migration|
|
38
|
+
migration[:filepath]
|
39
|
+
end.sort
|
40
|
+
|
41
|
+
filtered_and_sorted_files.each do |file|
|
42
|
+
migrate_file file, db
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def migrate_file path, db_handle = nil
|
47
|
+
filename = File.basename path, '.cast'
|
48
|
+
version, database = filename.split '.'
|
49
|
+
|
50
|
+
db = db_handle || (CouchRest.database! "http://#{Caster.config[:host]}:#{Caster.config[:port]}/#{database}")
|
51
|
+
metadoc = nil
|
52
|
+
begin
|
53
|
+
metadoc = db.get "#{Caster.config[:metadoc_id_prefix]}_#{database}"
|
54
|
+
rescue
|
55
|
+
metadoc = {
|
56
|
+
'_id' => "#{Caster.config[:metadoc_id_prefix]}_#{database}",
|
57
|
+
'type' => "#{Caster.config[:metadoc_type]}"
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
if metadoc['version'] != nil and version <= metadoc['version']
|
62
|
+
raise 'Cannot migrate down!'
|
63
|
+
else
|
64
|
+
metadoc['version'] = version
|
65
|
+
end
|
66
|
+
|
67
|
+
migrate_script database, File.open(path).read
|
68
|
+
|
69
|
+
db.save_doc metadoc
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Caster
|
2
|
+
|
3
|
+
# a migration operation applies a transformation over a document and saves it to a database
|
4
|
+
class Operation
|
5
|
+
|
6
|
+
def initialize db_handle, transformation
|
7
|
+
@db_handle = db_handle
|
8
|
+
@transformation = transformation
|
9
|
+
end
|
10
|
+
|
11
|
+
def db_handle
|
12
|
+
@db_handle
|
13
|
+
end
|
14
|
+
|
15
|
+
def transformation
|
16
|
+
@transformation
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Caster
|
2
|
+
class Reference
|
3
|
+
|
4
|
+
# to enable passing over method calls on the reference through method_missing
|
5
|
+
Object.instance_methods.each do |m|
|
6
|
+
undef_method m unless ['__send__', '__id__', 'object_id', 'is_a?'].include? m.to_s
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize db_handle, view, query
|
10
|
+
rdocs = db_handle.view(view, query)['rows']
|
11
|
+
@docs = rdocs.map { |rdoc| rdoc['value'] }
|
12
|
+
@post_eval_calls = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def where &predicate
|
16
|
+
@predicate = predicate
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing method_name, *args
|
21
|
+
@post_eval_calls << [method_name, args]
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def evaluate target_doc
|
26
|
+
@docs.each do |doc|
|
27
|
+
if @predicate.call doc
|
28
|
+
value = deref(doc, @value_field)
|
29
|
+
@post_eval_calls.each do |args|
|
30
|
+
value = value.send args[0], *args[1]
|
31
|
+
end
|
32
|
+
return value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def deref doc, accessor
|
40
|
+
return doc if accessor == nil
|
41
|
+
value = eval 'doc' << accessor.split('.').map { |field| "['#{field}']" }.join
|
42
|
+
(value.is_a? Fixnum)? value : value.clone
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'couchrest'
|
2
|
+
|
3
|
+
module Caster
|
4
|
+
class Add
|
5
|
+
|
6
|
+
def initialize field, value
|
7
|
+
@field = field
|
8
|
+
@value = value
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute doc
|
12
|
+
doc[@field] = evaluate(@value, doc)
|
13
|
+
doc
|
14
|
+
end
|
15
|
+
|
16
|
+
def evaluate obj, target_doc
|
17
|
+
(obj.is_a? Reference)? obj.evaluate(target_doc) : obj
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Caster
|
2
|
+
class Create
|
3
|
+
|
4
|
+
def initialize params
|
5
|
+
@params_template = params
|
6
|
+
end
|
7
|
+
|
8
|
+
def execute doc
|
9
|
+
params = @params_template.clone
|
10
|
+
params.each do |field, value|
|
11
|
+
if value.is_a? Reference
|
12
|
+
params[field] = value.evaluate doc
|
13
|
+
end
|
14
|
+
end
|
15
|
+
params
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: caster
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 59
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 9
|
9
|
+
- 0
|
10
|
+
version: 0.9.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Manohar Akula
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-08-13 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: couchrest
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: thor
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id002
|
48
|
+
description: A migration framework for couchdb databases
|
49
|
+
email: manohar.akula@gmail.com
|
50
|
+
executables:
|
51
|
+
- cast
|
52
|
+
extensions: []
|
53
|
+
|
54
|
+
extra_rdoc_files: []
|
55
|
+
|
56
|
+
files:
|
57
|
+
- lib/caster.rb
|
58
|
+
- lib/caster/execution.rb
|
59
|
+
- lib/caster/migration.rb
|
60
|
+
- lib/caster/migrator.rb
|
61
|
+
- lib/caster/operation.rb
|
62
|
+
- lib/caster/ref/reference.rb
|
63
|
+
- lib/caster/transform/add.rb
|
64
|
+
- lib/caster/transform/clone.rb
|
65
|
+
- lib/caster/transform/create.rb
|
66
|
+
- lib/caster/transform/delete.rb
|
67
|
+
- lib/caster/transform/remove.rb
|
68
|
+
- lib/caster/transform/rename.rb
|
69
|
+
- bin/cast
|
70
|
+
homepage: http://github.com/akula1001/caster
|
71
|
+
licenses: []
|
72
|
+
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options: []
|
75
|
+
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
hash: 3
|
84
|
+
segments:
|
85
|
+
- 0
|
86
|
+
version: "0"
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
hash: 3
|
93
|
+
segments:
|
94
|
+
- 0
|
95
|
+
version: "0"
|
96
|
+
requirements: []
|
97
|
+
|
98
|
+
rubyforge_project:
|
99
|
+
rubygems_version: 1.8.10
|
100
|
+
signing_key:
|
101
|
+
specification_version: 3
|
102
|
+
summary: Casters for your couch database
|
103
|
+
test_files: []
|
104
|
+
|