couchrest_changes 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,111 @@
1
+ CouchRest::Changes - keeping track of changes to your couch
2
+ ------------------------------------------------------------
3
+
4
+ ``CouchRest::Changes`` let's you observe a couch database for changes and react
5
+ upon them.
6
+
7
+ Following the changes of a couch is as easy as
8
+ ```ruby
9
+ users = CouchRest::Changes.new('users')
10
+ ```
11
+
12
+ Callbacks can be defined in blocks:
13
+ ```ruby
14
+ users.created do |hash|
15
+ puts "A new user was created with the id: #{hash[:id]}"
16
+ end
17
+ ```
18
+
19
+ To start listening just call
20
+ ```ruby
21
+ users.listen
22
+ ```
23
+
24
+ This program is copyright 2015 LEAP Encryption Access Project. It distributed
25
+ under the same license as CouchRest (Apache License, Version 2.0
26
+ http://www.apache.org/licenses/).
27
+
28
+ Installation
29
+ ---------------------
30
+
31
+ Just add couchrest_changes to your Gemfile.
32
+
33
+ Configuration
34
+ ---------------------
35
+
36
+ ``couchrest_changes`` can be configured through ``CouchRest::Changes::Config``
37
+
38
+ The default options are similar to the ones used by CouchRest::Model:
39
+
40
+ ```yaml
41
+ # couch connection configuration
42
+ connection:
43
+ protocol: "http"
44
+ host: "localhost"
45
+ port: 5984
46
+ username: ~
47
+ password: ~
48
+ prefix: ""
49
+ suffix: ""
50
+ netrc: ""
51
+
52
+ # directory to store the last processed record sequence
53
+ # so we can resume after a restart:
54
+ seq_dir: "/var/run/couch_changes"
55
+
56
+ # Configure log_file like this if you want to log to a file instead of syslog:
57
+ # log_file: "/var/log/couch_changes.log"
58
+ log_level: debug
59
+
60
+ options:
61
+ your_own_options: "go here"
62
+ ```
63
+
64
+ Normally, CouchRest leaks the CouchDB authentication credentials in the
65
+ process list. If present, the ``netrc`` configuration value will allow
66
+ CouchRest::Changes to use the netrc file and keep the credentials out of the
67
+ process list.
68
+
69
+ Running Tests
70
+ ------------------------
71
+
72
+ In order for the CouchRest::Changes tests to run, a local CouchDB must be
73
+ running in "admin party" mode. The tests themselves require that there is no
74
+ admin party, but the tests will correctly disable the admin party (by creating
75
+ a super admin called 'superadmin' with password 'secret').
76
+
77
+ After the tests have run, the admin party is restored. If something goes
78
+ wrong, you should remove the superadmin line at the end of
79
+ /etc/couchdb/local.ini and restart couchdb.
80
+
81
+ Examples
82
+ ------------------------
83
+
84
+ See [tapicero](https://github.com/leapcode/tapicero) for a daemon that uses
85
+ CouchRest::Changes. Historically CouchRest::Changes was extracted from
86
+ tapicero.
87
+
88
+ Known Issues
89
+ -------------
90
+
91
+ * CouchRest will miss the first change in a continuous feed.
92
+ https://github.com/couchrest/couchrest/pull/104 has a fix.
93
+ You might want to monkeypatch it.
94
+
95
+ Changes
96
+ -------------------------
97
+
98
+ 0.2.0
99
+
100
+ UPGRADING: the configuration 'seq_file' is no longer used,
101
+ but 'seq_dir' is required.
102
+
103
+ * hide password and username from process list by monkeypatching couchrest.
104
+ * support for listening on changes to multiple databases (new sequence file
105
+ format, so sequence processing will start from scratch when upgrading).
106
+ * automatically disable admin party when running tests
107
+
108
+ 0.1.1
109
+
110
+ * recover gracefully if couchdb returns an invalid sequence (as bigcouchd does
111
+ sometimes)
data/Rakefile CHANGED
@@ -76,7 +76,7 @@ end
76
76
  ##
77
77
 
78
78
  Rake::TestTask.new do |t|
79
- t.pattern = "test/unit/*_test.rb"
79
+ t.pattern = "test/**/*_test.rb"
80
80
  end
81
81
  task :default => :test
82
82
 
@@ -7,7 +7,7 @@ module CouchRest
7
7
 
8
8
  attr_writer :app_name
9
9
  attr_accessor :connection
10
- attr_accessor :seq_file
10
+ attr_accessor :seq_dir
11
11
  attr_accessor :log_file
12
12
  attr_writer :log_level
13
13
  attr_accessor :logger
@@ -20,6 +20,9 @@ module CouchRest
20
20
  file = find_file(file_path)
21
21
  load_config(file)
22
22
  end
23
+ unless loaded.compact.any?
24
+ raise ArgumentError.new("Could not find config file")
25
+ end
23
26
  self.flags ||= []
24
27
  init_logger
25
28
  log_loaded_configs(loaded.compact)
@@ -27,13 +30,21 @@ module CouchRest
27
30
  return self
28
31
  end
29
32
 
30
- def couch_host(conf = nil)
31
- conf ||= connection
33
+ def couch_host(options = nil)
34
+ if options
35
+ conf = connection.merge(options)
36
+ else
37
+ conf = connection
38
+ end
32
39
  userinfo = [conf[:username], conf[:password]].compact.join(':')
33
40
  userinfo += '@' unless userinfo.empty?
34
41
  "#{conf[:protocol]}://#{userinfo}#{conf[:host]}:#{conf[:port]}"
35
42
  end
36
43
 
44
+ def couch_host_no_auth
45
+ couch_host connection.merge({:password => nil, :username => nil})
46
+ end
47
+
37
48
  def couch_host_without_password
38
49
  couch_host connection.merge({:password => nil})
39
50
  end
@@ -63,7 +74,7 @@ module CouchRest
63
74
  end
64
75
 
65
76
  def load_config(file_path)
66
- return unless file_path
77
+ return nil unless file_path
67
78
  load_settings YAML.load(File.read(file_path)), file_path
68
79
  return file_path
69
80
  end
@@ -1,19 +1,60 @@
1
1
  module CouchRest::Changes
2
+
3
+ #
4
+ # CouchRest uses curl for 'streaming' requests
5
+ # (requests with a block passed to the db).
6
+ #
7
+ # Unfortunately, this leaks the username and password in the process list.
8
+ # We don't want to do this. So, we create two separate CouchRest::Database
9
+ # instances: one that is for normal requests and one that is used for
10
+ # streaming requests. The streaming one we hack to use netrc file in order
11
+ # to keep authentication info out of the process list.
12
+ #
13
+ # If no netrc file is configure, then this DatabaseProxy just uses the
14
+ # regular db.
15
+ #
16
+ class DatabaseProxy
17
+ def initialize(db_name)
18
+ @db = CouchRest.new(Config.couch_host).database(db_name)
19
+ unless @db
20
+ Config.logger.error "Database #{db_name} not found!"
21
+ raise RuntimeError "Database #{db_name} not found!"
22
+ end
23
+ if Config.connection[:netrc] && !Config.connection[:netrc].empty?
24
+ @db_stream = CouchRest.new(Config.couch_host_no_auth).database(db_name)
25
+ streamer = @db_stream.instance_variable_get('@streamer') # cheating, not exposed.
26
+ streamer.default_curl_opts += " --netrc-file \"#{Config.connection[:netrc]}\""
27
+ else
28
+ @db_stream = @db
29
+ end
30
+ end
31
+
32
+ def changes(*args, &block)
33
+ if block
34
+ @db_stream.changes(*args, &block)
35
+ else
36
+ @db.changes(*args)
37
+ end
38
+ end
39
+ end
40
+
2
41
  class Observer
3
42
 
4
43
  attr_writer :logger
44
+ attr_reader :since
5
45
 
6
46
  def initialize(db_name, options = {})
7
- db_name = Config.complete_db_name(db_name)
47
+ @db_name = Config.complete_db_name(db_name)
8
48
  info "Tracking #{db_name}"
9
49
  debug "Options: #{options.inspect}" if options.keys.any?
10
50
  @options = options
11
- unless @db = CouchRest.new(Config.couch_host).database(db_name)
12
- logger.error "Database #{db_name} not found!"
13
- raise RuntimeError "Database #{db_name} not found!"
51
+ @db = DatabaseProxy.new(@db_name)
52
+ setup_sequence_file(@db_name)
53
+ unless rerun?
54
+ @since = read_seq(@db_name)
55
+ else
56
+ @since = 0
14
57
  end
15
- read_seq(Config.seq_file) unless rerun?
16
- check_seq
17
58
  end
18
59
 
19
60
  # triggered when a document was newly created
@@ -40,24 +81,24 @@ module CouchRest::Changes
40
81
  info "listening..."
41
82
  debug "Starting at sequence #{since}"
42
83
  last = nil
43
- result = db.changes feed_options do |hash|
84
+ result = @db.changes(feed_options) do |hash|
44
85
  last = hash
45
86
  @retry_count = 0
46
87
  callbacks(hash) if hash_for_change?(hash)
47
- store_seq(hash["seq"])
88
+ store_seq(@db_name, hash["seq"])
48
89
  end
49
90
  raise EOFError
50
91
  # appearently MultiJson has issues with the end of the couch stream.
51
92
  # So sometimes we get a MultiJson::LoadError instead...
52
- rescue MultiJson::LoadError, EOFError, RestClient::ServerBrokeConnection
53
- info "Couch stream ended."
54
- debug result.inspect
55
- debug last.inspect
93
+ rescue MultiJson::LoadError, EOFError, RestClient::ServerBrokeConnection => exc
94
+ error "Couch stream ended - #{exc.class}"
95
+ debug result.inspect if result
96
+ debug last.inspect if last
56
97
  retry if retry_without_sequence?(result, last) || retry_later?
57
98
  end
58
99
 
59
100
  def last_sequence
60
- hash = db.changes :limit => 1, :descending => true
101
+ hash = @db.changes :limit => 1, :descending => true
61
102
  return hash["last_seq"]
62
103
  end
63
104
 
@@ -71,10 +112,6 @@ module CouchRest::Changes
71
112
  end.merge @options
72
113
  end
73
114
 
74
- def since
75
- @since ||= 0 # last_sequence
76
- end
77
-
78
115
  def callbacks(hash)
79
116
  # let's not track design document changes
80
117
  return if hash['id'].start_with? '_design/'
@@ -94,33 +131,57 @@ module CouchRest::Changes
94
131
  end
95
132
  end
96
133
 
97
- def read_seq(filename)
134
+ #
135
+ # ensure the sequence file exists
136
+ #
137
+ def setup_sequence_file(db_name)
138
+ filename = sequence_file_name(db_name)
139
+ unless Dir.exists?(Config.seq_dir)
140
+ FileUtils.mkdir_p(Config.seq_dir)
141
+ unless Dir.exists?(Config.seq_dir)
142
+ raise StandardError.new("Can't create sequence directory #{Config.seq_dir}")
143
+ end
144
+ end
145
+ unless File.exists?(filename)
146
+ FileUtils.touch(filename)
147
+ unless File.writable?(filename)
148
+ raise StandardError.new("Can't write to sequence file #{filename}")
149
+ end
150
+ end
151
+ end
152
+
153
+ #
154
+ # reads the sequence file, e.g. (/var/run/tapicero/users.seq), returning
155
+ # the sequence number or zero if the sequence number could not be
156
+ # determined.
157
+ #
158
+ def read_seq(db_name)
159
+ filename = sequence_file_name(db_name)
98
160
  debug "Looking up sequence here: #{filename}"
99
- FileUtils.touch(filename)
100
- unless File.writable?(filename)
101
- raise StandardError.new("Can't write to sequence file #{filename}")
161
+ result = File.read(filename)
162
+ if result.empty?
163
+ debug "Found no sequence in the file."
164
+ return 0
165
+ else
166
+ debug "Found sequence: #{result}"
167
+ return result.to_i
102
168
  end
103
- @since = File.read(filename)
104
169
  rescue Errno::ENOENT => e
105
170
  warn "No sequence file found. Starting from scratch"
171
+ return 0
106
172
  end
107
173
 
108
- def check_seq
109
- if @since == ''
110
- @since = nil
111
- debug "Found no sequence in the file."
112
- elsif @since
113
- debug "Found sequence: #{@since}"
114
- end
174
+ def store_seq(db_name, seq)
175
+ File.write sequence_file_name(db_name), seq.to_i
115
176
  end
116
177
 
117
- def store_seq(seq)
118
- File.write Config.seq_file, MultiJson.dump(seq)
178
+ def sequence_file_name(db_name)
179
+ File.join(Config.seq_dir, db_name + '.seq')
119
180
  end
120
181
 
121
182
  def retry_without_sequence?(result, last_hash)
122
183
  if malformated_sequence?(result) || malformated_sequence?(last_hash)
123
- @since = nil
184
+ @since = 0
124
185
  info "Trying to start from scratch."
125
186
  end
126
187
  end
@@ -165,6 +226,11 @@ module CouchRest::Changes
165
226
  logger.warn message
166
227
  end
167
228
 
229
+ def error(message)
230
+ return unless log_attempt?
231
+ logger.error message
232
+ end
233
+
168
234
  # let's not clutter the logs if couch is down for a longer time.
169
235
  def log_attempt?
170
236
  [0, 1, 2, 4, 8, 20, 40, 120].include?(retry_count) ||
@@ -179,9 +245,5 @@ module CouchRest::Changes
179
245
  logger ||= Config.logger
180
246
  end
181
247
 
182
- def db
183
- @db
184
- end
185
-
186
248
  end
187
249
  end
@@ -1,5 +1,5 @@
1
1
  module CouchRest
2
2
  module Changes
3
- VERSION = "0.1.1"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
data/test/config.yaml CHANGED
@@ -7,13 +7,11 @@ connection:
7
7
  protocol: "http"
8
8
  host: "localhost"
9
9
  port: 5984
10
- username: anna
11
- password: secret
10
+ username: "anna"
11
+ password: "secret"
12
12
  prefix: "couchrest_changes_test"
13
13
  suffix: ""
14
14
 
15
- # file to store the last processed user record in so we can resume after
16
- # a restart:
17
- seq_file: "/tmp/couchrest_changes_test.seq"
15
+ seq_dir: "/tmp/couchrest_changes"
18
16
  log_file: "/tmp/couchrest_changes_test.log"
19
17
  log_level: debug
@@ -1,34 +1,91 @@
1
- require 'test_helper'
1
+ require_relative '../test_helper'
2
2
 
3
3
  class CallbackTest < CouchRest::Changes::IntegrationTest
4
4
 
5
5
  def setup
6
6
  super
7
+ @config = CouchRest::Changes::Config
8
+ @config.load BASE_DIR, 'test/config.yaml'
7
9
  @config.flags = ['--run-once']
8
- @changes = CouchRest::Changes.new 'records'
9
- File.write config.seq_file, @changes.last_sequence
10
10
  end
11
11
 
12
12
  def teardown
13
- delete
14
13
  end
15
14
 
16
15
  def test_triggers_created
17
16
  handler = mock 'handler'
18
17
  handler.expects(:callback).once
19
- @changes.created { |hash| handler.callback(hash) }
20
- create
21
- @changes.listen
18
+
19
+ changes = CouchRest::Changes.new 'records'
20
+ changes.created { handler.callback }
21
+ File.write sequence_file, changes.last_sequence
22
+
23
+ db_connect('records', @config) do |db|
24
+ db.create_record
25
+ changes.listen
26
+ db.delete_record
27
+ end
28
+ end
29
+
30
+ #
31
+ # CouchRest::Changes.new will apply whatever values are in
32
+ # the current Config.
33
+ #
34
+ def test_netrc
35
+ db_connect('records', @config) do |db|
36
+ #
37
+ # test with a bad netrc.
38
+ #
39
+ # I wish we could test for RestClient::Unauthorized
40
+ # but CouchRest just silently eats Couch's
41
+ # {"error"=>"unauthorized"} response and returns as
42
+ # if there was no problem. Grrr.
43
+ #
44
+ handler = mock 'handler'
45
+ handler.expects(:callback).never
46
+ @config.connection[:netrc] = "error"
47
+ changes = CouchRest::Changes.new 'records'
48
+ File.write sequence_file, changes.last_sequence
49
+ changes.created {handler.callback}
50
+ db.create_record
51
+ changes.listen
52
+ db.delete_record
53
+
54
+ #
55
+ # now test with a good netrc.
56
+ #
57
+ handler = mock 'handler'
58
+ handler.expects(:callback).once
59
+ @config.connection[:netrc] = File.expand_path("../../test.netrc", __FILE__)
60
+ changes = CouchRest::Changes.new 'records'
61
+ changes.created { handler.callback}
62
+ File.write sequence_file, changes.last_sequence
63
+ db.create_record
64
+ changes.listen
65
+ db.delete_record
66
+ end
22
67
  end
23
68
 
24
69
  def test_starts_from_scratch_on_invalid_sequence
25
- File.write config.seq_file, "invalid string"
26
- @changes = CouchRest::Changes.new 'records'
27
70
  handler = mock 'handler'
28
71
  handler.expects(:callback).at_least_once
29
- @changes.created { |hash| handler.callback(hash) }
30
- create
31
- @changes.listen
72
+
73
+ File.write sequence_file, "invalid string"
74
+ changes = CouchRest::Changes.new 'records'
75
+ changes.created { |hash| handler.callback(hash) }
76
+
77
+ db_connect('records', @config) do |db|
78
+ db.create_record
79
+ changes.listen
80
+ db.delete_record
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def sequence_file(db_name='records')
87
+ name = CouchRest::Changes::Config.complete_db_name(db_name)
88
+ @config.seq_dir + '/' + name + '.seq'
32
89
  end
33
90
 
34
91
  end
@@ -0,0 +1,85 @@
1
+ #
2
+ # Setup CouchDB for testing.
3
+ #
4
+
5
+ TEST_DB_NAME = 'records'
6
+
7
+ def save_doc(db, record)
8
+ id = record["_id"]
9
+ db.save_doc(record)
10
+ puts " * created #{db.name}/#{id}"
11
+ rescue RestClient::Conflict
12
+ puts " * #{db.name}/#{id} already exists"
13
+ rescue RestClient::Exception => exc
14
+ puts " * Error saving #{db.name}/#{id}: #{exc}"
15
+ end
16
+
17
+ def super_host(config)
18
+ config.couch_host(:username => 'superadmin', :password => 'secret')
19
+ end
20
+
21
+ # remove prior superadmin, if it happens to exist
22
+ def remove_super_admin(config)
23
+ begin
24
+ CouchRest.delete(super_host(config) + "/_config/admins/superadmin")
25
+ puts " * removed superadmin"
26
+ rescue RestClient::ResourceNotFound
27
+ rescue RestClient::Unauthorized
28
+ rescue RestClient::Exception => exc
29
+ puts " * Unable to remove superadmin from CouchDB: #{exc}"
30
+ end
31
+ end
32
+
33
+ # add superadmin to remove admin party
34
+ def create_super_admin(config)
35
+ begin
36
+ CouchRest.put(config.couch_host_no_auth + "/_config/admins/superadmin", 'secret')
37
+ puts " * created superadmin"
38
+ rescue RestClient::ResourceNotFound
39
+ rescue RestClient::Exception => exc
40
+ puts " * Unable to add superadmin from CouchDB: #{exc}"
41
+ end
42
+ end
43
+
44
+ def setup_couchdb
45
+ CouchRest::Changes::Config.load(BASE_DIR, 'test/config.yaml').tap do |config|
46
+ remove_super_admin(config)
47
+ create_super_admin(config)
48
+ CouchRest.new(super_host(config)).database('_users').tap do |db|
49
+ # create unprivileged user
50
+ save_doc(db, {
51
+ "_id" => "org.couchdb.user:me",
52
+ "name" => "me",
53
+ "roles" => ["normal"],
54
+ "type" => "user",
55
+ "password" => "password"
56
+ })
57
+ # create privileged user
58
+ save_doc(db, {
59
+ "_id" => "org.couchdb.user:anna",
60
+ "name" => "anna",
61
+ "roles" => ["admin"],
62
+ "type" => "user",
63
+ "password" => "secret"
64
+ })
65
+ end
66
+ CouchRest.new(super_host(config)).database(config.complete_db_name(TEST_DB_NAME)).tap do |db|
67
+ db.create!
68
+ puts " * created db #{db}"
69
+ save_doc(db, {
70
+ "_id" => "_security",
71
+ "admins" => {
72
+ "names" => ["anna"],
73
+ "roles" => ["admin"]
74
+ },
75
+ "members" => {
76
+ "names" => ["me"],
77
+ "roles" => ["normal"]
78
+ }
79
+ })
80
+ end
81
+ Minitest.after_run do
82
+ remove_super_admin(config)
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,30 @@
1
+ module CouchRest::Changes
2
+
3
+ class TestDatabase
4
+ def initialize(url, db_name)
5
+ @db = CouchRest.new(url).database(db_name)
6
+ @record = nil
7
+ end
8
+
9
+ def create_record(fast = false)
10
+ result = @db.save_doc :some => :content
11
+ raise RuntimeError.new(result.inspect) unless result['ok']
12
+ @record = {'_id' => result["id"], '_rev' => result["rev"]}
13
+ sleep 0.25
14
+ end
15
+
16
+ def delete_record(fast = false)
17
+ return if @record.nil? or @record['_deleted']
18
+ result = @db.delete_doc @record
19
+ raise RuntimeError.new(result.inspect) unless result['ok']
20
+ @record['_deleted'] = true
21
+ end
22
+ end
23
+
24
+ class IntegrationTest < MiniTest::Test
25
+ def db_connect(db_name, config)
26
+ yield TestDatabase.new(config.couch_host, config.complete_db_name(db_name))
27
+ end
28
+ end
29
+
30
+ end
data/test/test.netrc ADDED
@@ -0,0 +1,3 @@
1
+ machine localhost
2
+ login anna
3
+ password secret
data/test/test_helper.rb CHANGED
@@ -1,11 +1,18 @@
1
1
  require 'rubygems'
2
+ gem 'minitest'
2
3
  require 'minitest/autorun'
3
4
 
4
5
  BASE_DIR = File.expand_path('../..', __FILE__)
5
6
  $:.unshift File.expand_path('lib', BASE_DIR)
7
+ $:.unshift File.dirname(__FILE__)
6
8
 
7
9
  require 'couchrest/changes'
8
- require 'support/integration_test'
9
-
10
+ require 'support/integration_helper'
10
11
  require 'mocha/setup'
12
+ require 'setup_couchdb'
11
13
 
14
+ puts
15
+ puts " SETTING UP COUCHDB FOR TESTS:"
16
+ puts
17
+ setup_couchdb()
18
+ puts
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: couchrest_changes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-09-09 00:00:00.000000000 Z
12
+ date: 2015-03-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: couchrest
@@ -66,7 +66,7 @@ dependencies:
66
66
  requirements:
67
67
  - - ~>
68
68
  - !ruby/object:Gem::Version
69
- version: 3.2.0
69
+ version: 5.4.0
70
70
  type: :development
71
71
  prerelease: false
72
72
  version_requirements: !ruby/object:Gem::Requirement
@@ -74,7 +74,7 @@ dependencies:
74
74
  requirements:
75
75
  - - ~>
76
76
  - !ruby/object:Gem::Version
77
- version: 3.2.0
77
+ version: 5.4.0
78
78
  - !ruby/object:Gem::Dependency
79
79
  name: mocha
80
80
  requirement: !ruby/object:Gem::Requirement
@@ -131,17 +131,18 @@ executables: []
131
131
  extensions: []
132
132
  extra_rdoc_files: []
133
133
  files:
134
+ - lib/couchrest/changes.rb
134
135
  - lib/couchrest/changes/config.rb
135
- - lib/couchrest/changes/version.rb
136
136
  - lib/couchrest/changes/observer.rb
137
- - lib/couchrest/changes.rb
137
+ - lib/couchrest/changes/version.rb
138
138
  - Rakefile
139
- - Readme.md
140
- - test/integration/callback_test.rb
141
- - test/setup_couch.sh
139
+ - README.md
140
+ - test/setup_couchdb.rb
142
141
  - test/config.yaml
143
- - test/support/integration_test.rb
144
142
  - test/test_helper.rb
143
+ - test/support/integration_helper.rb
144
+ - test/test.netrc
145
+ - test/integration/callback_test.rb
145
146
  homepage: https://leap.se
146
147
  licenses: []
147
148
  post_install_message:
@@ -156,7 +157,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
156
157
  version: '0'
157
158
  segments:
158
159
  - 0
159
- hash: -2972848587685415057
160
+ hash: 3909050601515988109
160
161
  required_rubygems_version: !ruby/object:Gem::Requirement
161
162
  none: false
162
163
  requirements:
@@ -165,17 +166,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
165
166
  version: '0'
166
167
  segments:
167
168
  - 0
168
- hash: -2972848587685415057
169
+ hash: 3909050601515988109
169
170
  requirements: []
170
171
  rubyforge_project:
171
- rubygems_version: 1.8.28
172
+ rubygems_version: 1.8.23
172
173
  signing_key:
173
174
  specification_version: 3
174
175
  summary: CouchRest::Changes - Observe a couch database for changes and react upon
175
176
  them
176
177
  test_files:
177
- - test/integration/callback_test.rb
178
- - test/setup_couch.sh
178
+ - test/setup_couchdb.rb
179
179
  - test/config.yaml
180
- - test/support/integration_test.rb
181
180
  - test/test_helper.rb
181
+ - test/support/integration_helper.rb
182
+ - test/test.netrc
183
+ - test/integration/callback_test.rb
data/Readme.md DELETED
@@ -1,75 +0,0 @@
1
- CouchRest::Changes - keeping track of changes to your couch
2
- ------------------------------------------------------------
3
-
4
- ``CouchRest::Changes`` let's you observe a couch database for changes and react upon them.
5
-
6
- Following the changes of a couch is as easy as
7
- ```ruby
8
- users = CouchRest::Changes.new('users')
9
- ```
10
-
11
- Callbacks can be defined in blocks:
12
- ```ruby
13
- users.created do |hash|
14
- puts "A new user was created with the id: #{hash[:id]}"
15
- end
16
- ```
17
-
18
- To start listening just call
19
- ```ruby
20
- users.listen
21
- ```
22
-
23
- This program is written in Ruby and is distributed under the following license:
24
-
25
- > GNU Affero General Public License
26
- > Version 3.0 or higher
27
- > http://www.gnu.org/licenses/agpl-3.0.html
28
-
29
- Installation
30
- ---------------------
31
-
32
- Just add couchrest_changes to your gemfile.
33
-
34
- Configuration
35
- ---------------------
36
-
37
- ``couchrest_changes`` can be configured through ``CouchRest::Changes::Config``
38
-
39
- The default options are similar to the ones used by CouchRest::Model:
40
-
41
-
42
- ```yaml
43
- # couch connection configuration
44
- connection:
45
- protocol: "http"
46
- host: "localhost"
47
- port: 5984
48
- username: ~
49
- password: ~
50
- prefix: ""
51
- suffix: ""
52
-
53
- # file to store the last processed user record in so we can resume after
54
- # a restart:
55
- seq_file: "/var/log/couch_changes_users.seq"
56
-
57
- # Configure log_file like this if you want to log to a file instead of syslog:
58
- # log_file: "/var/log/couch_changes.log"
59
- log_level: debug
60
-
61
- options:
62
- your_own_options: "go here"
63
- ```
64
-
65
- Examples
66
- ------------------------
67
-
68
- See [tapicero](https://github.com/leapcode/tapicero) for a daemon that uses CouchRest::Changes. Historically CouchRest::Changes was extracted from tapicero.
69
-
70
- Known Issues
71
- -------------
72
-
73
- * CouchRest will miss the first change in a continuous feed.
74
- https://github.com/couchrest/couchrest/pull/104 has a fix.
75
- You might want to monkeypatch it.
data/test/setup_couch.sh DELETED
@@ -1,13 +0,0 @@
1
- #!/bin/bash
2
-
3
- HOST="http://localhost:5984"
4
- echo "couch version :"
5
- curl -X GET $HOST
6
- echo "creating unprivileged user :"
7
- curl -HContent-Type:application/json -XPUT $HOST/_users/org.couchdb.user:me --data-binary '{"_id": "org.couchdb.user:me","name": "me","roles": [],"type": "user","password": "pwd"}'
8
- echo "creating database to watch:"
9
- curl -X PUT $HOST/couchrest_changes_test_records
10
- echo "restricting database access :"
11
- curl -X PUT $HOST/couchrest_changes_test_records/_security -Hcontent-type:application/json --data-binary '{"admins":{"names":[],"roles":[]},"members":{"names":["me"],"roles":[]}}'
12
- echo "adding admin :"
13
- curl -X PUT $HOST/_config/admins/anna -d '"secret"'
@@ -1,39 +0,0 @@
1
- module CouchRest::Changes
2
- class IntegrationTest < MiniTest::Unit::TestCase
3
-
4
- attr_reader :config
5
-
6
- def setup
7
- @config ||= CouchRest::Changes::Config.load BASE_DIR,
8
- 'test/config.yaml'
9
- end
10
-
11
- def create(fast = false)
12
- result = database.save_doc :some => :content
13
- raise RuntimeError.new(result.inspect) unless result['ok']
14
- @record = {'_id' => result["id"], '_rev' => result["rev"]}
15
- sleep 1
16
- end
17
-
18
- def delete(fast = false)
19
- return if @record.nil? or @record['_deleted']
20
- result = database.delete_doc @record
21
- raise RuntimeError.new(result.inspect) unless result['ok']
22
- @record['_deleted'] = true
23
- sleep 1
24
- end
25
-
26
- def database
27
- @database ||= host.database(database_name)
28
- end
29
-
30
- def database_name
31
- config.complete_db_name('records')
32
- end
33
-
34
- def host
35
- @host ||= CouchRest.new(config.couch_host)
36
- end
37
-
38
- end
39
- end