couchrest_changes 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.
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