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.
@@ -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
@@ -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,13 @@
1
+ module Caster
2
+ class Clone
3
+
4
+ def initialize source
5
+ @source = source
6
+ end
7
+
8
+ def execute doc
9
+ @source.delete '_rev'
10
+ @source
11
+ end
12
+ end
13
+ 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
@@ -0,0 +1,8 @@
1
+ module Caster
2
+ class Delete
3
+
4
+ def execute doc
5
+ doc.merge '_deleted' => true
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,13 @@
1
+ module Caster
2
+ class Remove
3
+
4
+ def initialize field
5
+ @field = field
6
+ end
7
+
8
+ def execute doc
9
+ doc.delete @field
10
+ doc
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module Caster
2
+ class Rename
3
+
4
+ def initialize old_name, new_name
5
+ @old_name = old_name
6
+ @new_name = new_name
7
+ end
8
+
9
+ def execute doc
10
+ doc[@new_name] = doc[@old_name]
11
+ doc.delete @old_name
12
+ doc
13
+ end
14
+ end
15
+ 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
+