slipcover 0.1.1 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a8235e0960e7f322d83876b91dc94d0d34883831
4
- data.tar.gz: f16b709e7881cfc0458a74f1c0968bd9d2cd8ab2
3
+ metadata.gz: e49398586e21e510dbabb4125a7e6be20a2ce675
4
+ data.tar.gz: 8caf835e9eb85951484f9d9b1e1ef36c9540d59e
5
5
  SHA512:
6
- metadata.gz: 6fba538515310312341f92cff1438f8d54dfb18d8df22ff71e05942ee377bf06e6f7c32c45ec4a91dda88f7bc4fd6f176a78865ac72395d62802531b558b0ff5
7
- data.tar.gz: 0b431d90fd29e1db6ddb9a094e7d9024efda0cee6558643474718727dfa8b85ecf1972aa57e6d14034eca413095100c87d60fe22911de3766e41820efd0251d6
6
+ metadata.gz: a960e3e93453cd982db2c6195517c1f15ed71b7dc3185d43b748a2f31fbd53c4c2fadeab9431146c3171d56f0705252193a216a0aa4096259c305271d05a9fb4
7
+ data.tar.gz: 3ddda15b941a725632490bb1046d0b93b7af34aa99c01c7aea059f544bf54147721e6ea8cccc5db451fdfb9b155b679e68b22dde06ac6e697ed5d026d9642523
data/Rakefile CHANGED
@@ -1 +1,12 @@
1
1
  require "bundler/gem_tasks"
2
+
3
+ task :console do
4
+ require 'irb'
5
+ require 'irb/completion'
6
+ require 'slipcover'
7
+ Slipcover::Config.yaml_path = File.expand_path("../spec/support/slipcover.yml", __FILE__)
8
+ Slipcover::Config.view_dir = File.expand_path("../spec/support/slipcover_views", __FILE__)
9
+ ARGV.clear
10
+ IRB.start
11
+ end
12
+
@@ -0,0 +1,68 @@
1
+ require 'net/http'
2
+ require 'fileutils'
3
+ require 'logger'
4
+ require 'json'
5
+
6
+ module Slipcover
7
+ module DevTools
8
+
9
+ class Replicator
10
+ attr_reader :source_environment, :source_server
11
+ attr_reader :target_environment, :target_server
12
+ attr_reader :logger, :http
13
+
14
+ def initialize(source_environment = "staging", target_environment = "development", logger = Logger.new("/dev/null"))
15
+ @source_environment = source_environment
16
+ @source_server = Slipcover::Server.new(Slipcover::Config.yaml_path, source_environment)
17
+
18
+ @target_environment = target_environment
19
+ @target_server = Slipcover::Server.new(Slipcover::Config.yaml_path, target_environment)
20
+
21
+ @logger = logger
22
+ @http = HttpAdapter.new
23
+ end
24
+
25
+ def perform
26
+ logger.info "Fetching list of #{source_environment} databases..."
27
+ logger.info "Replicating these dbs: #{dbs_to_replicate.join(",")}"
28
+
29
+ dbs_to_replicate.each_with_index.map do |db, i|
30
+ Thread.new(db, i, &method(:replicate))
31
+ end.map(&:join)
32
+ end
33
+
34
+ def dbs_to_replicate
35
+ @dbs_to_replicate ||= begin
36
+ require 'slipcover/http_adapter'
37
+ dbs = http.get(source_server.url + "/_all_dbs")
38
+ dbs.select { |db| db.end_with?(source_environment) }
39
+ end
40
+ end
41
+
42
+ def replicate(source_db_name, th_id)
43
+ target_db_name = infer_target_db_name(source_db_name)
44
+ source_url = db_url(source_server, source_db_name)
45
+ target_url = db_url(target_server, target_db_name)
46
+
47
+ logger.info "#{th_id}: Replicating #{source_url} into #{target_url}"
48
+
49
+ response = http.post(target_server.url + "/_replicate", { source: source_url, target: target_url, create_target: true })
50
+
51
+ logger.debug("#{th_id}: " + response.to_json)
52
+
53
+ raise "#{th_id}: Replication of #{source_url} into #{target_url} failed" unless response[:ok]
54
+
55
+ logger.info "#{th_id}: Done replicating #{source_url} into #{target_url}"
56
+ end
57
+
58
+ def infer_target_db_name(source_db_name)
59
+ source_db_name.sub(/#{source_environment}$/, target_environment)
60
+ end
61
+
62
+ def db_url(server, db_name)
63
+ "http://" + server.url + "/" + db_name
64
+ end
65
+ end
66
+
67
+ end
68
+ end
@@ -46,10 +46,7 @@ module Slipcover
46
46
  end
47
47
 
48
48
  def database
49
- return @database if @database
50
- @database = Slipcover::Database.new(database_name)
51
- @database.create
52
- @database
49
+ @database ||= Slipcover::Database.new(database_name)
53
50
  end
54
51
 
55
52
  private
@@ -1,5 +1,11 @@
1
1
  module Slipcover
2
2
  class HttpAdapter
3
+ def head(url, data={})
4
+ try {
5
+ parse( RestClient.head(url + query_string(data), headers) )
6
+ }
7
+ end
8
+
3
9
  def get(url, data={})
4
10
  try {
5
11
  parse( RestClient.get(url + query_string(data), headers) )
@@ -26,7 +32,7 @@ module Slipcover
26
32
 
27
33
  def query_string(data)
28
34
  return "" if data.empty?
29
-
35
+
30
36
  query = data.map do |key, value|
31
37
  "#{key}=#{value}"
32
38
  end.join("&")
@@ -35,7 +41,8 @@ module Slipcover
35
41
  end
36
42
 
37
43
  def parse(response)
38
- JSON.parse(response).symbolize_keys
44
+ parsed = JSON.parse(response)
45
+ parsed.is_a?(Hash) ? parsed.symbolize_keys : parsed
39
46
  end
40
47
 
41
48
  def try
@@ -45,7 +52,15 @@ module Slipcover
45
52
  end
46
53
 
47
54
  def reraise(e)
48
- raise error_class(e).new(e.message)
55
+ # As we keep doing more stuff with couchdb we might want to update this list
56
+ response = JSON.parse(e.response) rescue Hash.new
57
+ case response["reason"]
58
+ when "no_db_file" then raise DBNotFound, e.response
59
+ when "missing_named_view" then raise NamedViewNotFound, e.response
60
+ when "missing" then raise DocumentNotFound, e.response
61
+ else
62
+ raise error_class(e).new(response.empty? ? e.message : e.response)
63
+ end
49
64
  end
50
65
 
51
66
  def error_class(e)
@@ -67,6 +82,15 @@ module Slipcover
67
82
  class NotFound < RuntimeError
68
83
  end
69
84
 
85
+ class DBNotFound < RuntimeError
86
+ end
87
+
88
+ class NamedViewNotFound < RuntimeError
89
+ end
90
+
91
+ class DocumentNotFound < RuntimeError
92
+ end
93
+
70
94
  def headers
71
95
  {:content_type => :json, :accept => :json}
72
96
  end
@@ -23,6 +23,8 @@ module Slipcover
23
23
  end
24
24
 
25
25
  def repackage(opts)
26
+ opts = opts.dup
27
+
26
28
  opts.each do |key, value|
27
29
  opts[key] = escape(value) if escape_key?(key)
28
30
  end
@@ -3,5 +3,9 @@ module Slipcover
3
3
  initializer "slipcover.configure_rails_initialization" do
4
4
  Slipcover::Config.rails!
5
5
  end
6
+
7
+ rake_tasks do
8
+ require File.dirname(__FILE__) + "/tasks.rb"
9
+ end
6
10
  end
7
11
  end
@@ -0,0 +1,4 @@
1
+ require 'slipcover'
2
+
3
+ load "#{File.dirname(__FILE__)}/tasks/pull.rake"
4
+
@@ -0,0 +1,12 @@
1
+ namespace :slipcover do
2
+ namespace :db do
3
+ desc "Pull all CouchDB databases from the target environment into the local CouchDB"
4
+ task :pull, [:stage_name] => (:environment if defined?(Rails)) do |t, args|
5
+ require 'slipcover/dev_tools'
6
+ logger = Logger.new(STDOUT)
7
+ logger.level = ENV["DEBUG"] ? Logger::DEBUG : Logger::INFO
8
+ stage = args[:stage_name] || "staging"
9
+ Slipcover::DevTools::Replicator.new(stage, "development", logger).perform
10
+ end
11
+ end
12
+ end
@@ -1,3 +1,3 @@
1
1
  module Slipcover
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -64,7 +64,7 @@ describe Slipcover::Database, 'functional to the Slipcover database' do
64
64
 
65
65
  expect {
66
66
  database.info
67
- }.to raise_error( Slipcover::HttpAdapter::NotFound )
67
+ }.to raise_error( Slipcover::HttpAdapter::DBNotFound )
68
68
  end
69
69
  end
70
70
 
@@ -29,4 +29,41 @@ describe Slipcover::HttpAdapter do
29
29
  adapter.delete('http://url.com', {foo: 'bar'})
30
30
  end
31
31
  end
32
+
33
+ describe "response processing" do
34
+ it "can digest json object result" do
35
+ RestClient.stub(:get).and_return('{ "foo": "bar" }')
36
+ adapter.get('http://url.com/db/something_returing_an_array').should == { foo: "bar" }
37
+ end
38
+
39
+ it "can digest json array result" do
40
+ RestClient.stub(:get).and_return("[123, 456]")
41
+ adapter.get('http://url.com/db/something_returing_an_array').should == [123, 456]
42
+ end
43
+ end
44
+
45
+ describe "edge cases" do
46
+ it "raises DBNotFound when the error reason is 'no_db_file'" do
47
+ RestClient.stub(:post).and_raise(RestClient::ResourceNotFound.new('{"error": "not_found", "reason": "no_db_file"}'))
48
+ expect do
49
+ adapter.post('http://url.com/non_existing_db/document_to_update', {})
50
+ end.to raise_error(Slipcover::HttpAdapter::DBNotFound)
51
+ end
52
+
53
+ it "raises NamedViewNotFound when the error reason is 'missing_named_view'" do
54
+ RestClient.stub(:post).and_raise(RestClient::ResourceNotFound.new('{"error": "not_found", "reason": "missing_named_view"}'))
55
+ expect do
56
+ adapter.post('http://url.com/existing_db/non_existing_document', {})
57
+ end.to raise_error(Slipcover::HttpAdapter::NamedViewNotFound)
58
+ end
59
+
60
+ it "raises DocumentNotFound when the error reason is 'missing'" do
61
+ RestClient.stub(:post).and_raise(RestClient::ResourceNotFound.new('{"error": "not_found", "reason": "missing"}'))
62
+ expect do
63
+ adapter.post('http://url.com/existing_db/non_existing_document', {})
64
+ end.to raise_error(Slipcover::HttpAdapter::DocumentNotFound)
65
+ end
66
+
67
+ # Need to test other exceptions!
68
+ end
32
69
  end
data/spec/query_spec.rb CHANGED
@@ -37,7 +37,7 @@ describe Slipcover::Query do
37
37
  context 'with options' do
38
38
  context 'with key' do
39
39
  it "returns only the related records" do
40
- results = query.all({key: 'Deepti'})
40
+ results = query.all(key: 'Deepti')
41
41
  results.size.should == 1
42
42
  results.first[:name].should == 'Deepti'
43
43
  end
@@ -45,9 +45,9 @@ describe Slipcover::Query do
45
45
 
46
46
  context 'with a startkey and endkey' do
47
47
  it "return the range of docs" do
48
- results = query.all({startkey: 'Da', endkey: 'Fz'})
48
+ results = query.all(startkey: 'Da', endkey: 'Fz')
49
49
  results.size.should == 2
50
- results.map{ |doc| doc[:name] }.should =~ ['Deepti', 'Fito']
50
+ results.map { |doc| doc[:name] }.should =~ ['Deepti', 'Fito']
51
51
  end
52
52
  end
53
53
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slipcover
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kane Baccigalupi
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2014-05-27 00:00:00.000000000 Z
14
+ date: 2014-06-04 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: rest-client
@@ -102,11 +102,14 @@ files:
102
102
  - lib/slipcover/config.rb
103
103
  - lib/slipcover/database.rb
104
104
  - lib/slipcover/design_document.rb
105
+ - lib/slipcover/dev_tools.rb
105
106
  - lib/slipcover/document.rb
106
107
  - lib/slipcover/http_adapter.rb
107
108
  - lib/slipcover/query.rb
108
109
  - lib/slipcover/railtie.rb
109
110
  - lib/slipcover/server.rb
111
+ - lib/slipcover/tasks.rb
112
+ - lib/slipcover/tasks/pull.rake
110
113
  - lib/slipcover/version.rb
111
114
  - slipcover.gemspec
112
115
  - spec/database_spec.rb