Class: Moneta::Adapters::Couch

Inherits:
Object
  • Object
show all
Includes:
Defaults
Defined in:
lib/moneta/adapters/couch.rb

Overview

CouchDB backend

You can store hashes directly using this adapter.

Examples:

Store hashes

db = Moneta::Adapters::Couch.new
db['key'] = {a: 1, b: 2}

Defined Under Namespace

Classes: HTTPError

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Defaults

#[], #[]=, #close, #decrement, #features, #fetch, #fetch_values, included, #increment, #supports?, #update

Methods included from OptionSupport

#expires, #prefix, #raw, #with

Constructor Details

#initialize(options = {}) ⇒ Couch

Returns a new instance of Couch.

Parameters:

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :host (String) — default: '127.0.0.1'

    Couch host

  • :port (String) — default: 5984

    Couch port

  • :db (String) — default: 'moneta'

    Couch database

  • :scheme (String) — default: 'http'

    HTTP scheme to use

  • :value_field (String) — default: 'value'

    Document field to store value

  • :type_field (String) — default: 'type'

    Document field to store value type

  • :login (String)

    Login name to use for HTTP basic authentication

  • :password (String)

    Password to use for HTTP basic authentication

  • :adapter (Symbol)

    Adapter to use with Faraday

  • :backend (Faraday::Connecton)

    Use existing backend instance

  • Other (Object)

    options passed to Faraday::new (unless :backend option is provided).


48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/moneta/adapters/couch.rb', line 48

def initialize(options = {})
  @value_field = options.delete(:value_field) || 'value'
  @type_field = options.delete(:type_field) || 'type'
   = options.delete(:login)
  password = options.delete(:password)
  @backend = options.delete(:backend) || begin
    host = options.delete(:host) || '127.0.0.1'
    port = options.delete(:port) || 5984
    db = options.delete(:db) || 'moneta'
    scheme = options.delete(:scheme) || 'http'
    block = if faraday_adapter = options.delete(:adapter)
              proc { |faraday| faraday.adapter(faraday_adapter) }
            end
    ::Faraday.new("#{scheme}://#{host}:#{port}/#{db}", options, &block)
  end
  @backend.basic_auth(, password) if  && password
  @rev_cache = Moneta.build do
    use :Lock
    adapter :LRUHash
  end
  create_db
end

Instance Attribute Details

#backendObject (readonly)


31
32
33
# File 'lib/moneta/adapters/couch.rb', line 31

def backend
  @backend
end

Instance Method Details

#clear(options = {}) ⇒ void

This method returns an undefined value.

Clear all keys in this store

Parameters:

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :compact (Boolean) — default: false

    Whether to compact the database after clearing

  • :await_compact (Boolean) — default: false

    Whether to wait for compaction to complete before returning.

  • :full_commit (Boolean) — default: nil

    Set to `true` or `false` to override the server's href="https://docs.couchdb.org/en/stable/config/couchdb.html#couchdb/delayed_commits">docs.couchdb.org/en/stable/config/couchdb.html#couchdb/delayed_commits

    commit policy
    

143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/moneta/adapters/couch.rb', line 143

def clear(options = {})
  loop do
    docs = all_docs(limit: 10_000)
    break if docs['rows'].empty?
    deletions = docs['rows'].map do |row|
      { _id: row['id'], _rev: row['value']['rev'], _deleted: true }
    end
    bulk_docs(deletions, full_commit: options[:full_commit])
  end

  # Compact the database unless told not to
  if options[:compact]
    post('_compact', expect: 202)

    # Performance won't be great while compaction is happening, so by default we wait for it
    if options[:await_compact]
      loop do
        db_info = get('', expect: 200)
        break unless db_info['compact_running']

        # wait before checking again
        sleep 1
      end
    end
  end

  self
end

#create(key, value, options = {}) ⇒ Boolean

Note:

Not every Moneta store implements this method, a NotImplementedError is raised if it is not supported.

Atomically sets a key to value if it's not set.

Parameters:

  • key (Object)
  • value (Object)
  • options (Hash) (defaults to: {})

Options Hash (options):

  • :expires (Integer)

    Update expiration time (See Expires)

  • :raw (Boolean)

    Raw access without value transformation (See Transformer)

  • :prefix (String)

    Prefix key (See Transformer)

  • :expires (Integer)

    Update expiration time (See Expires)

  • :prefix (String)

    Prefix key (See Transformer)

  • Other (Object)

    options as defined by the adapters or middleware

  • :cache_rev (Boolean) — default: true

    Whether to cache the rev of the document for faster updating

Returns:

  • (Boolean)

    key was set


174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/moneta/adapters/couch.rb', line 174

def create(key, value, options = {})
  cache_rev = options[:cache_rev] != false
  doc = value_to_doc(value, nil)
  response = put(key, doc, cache_rev: cache_rev, returns: :response)
  case response.status
  when 201
    true
  when 409
    false
  else
    raise HTTPError.new(response.status, :put, @backend.create_url(key))
  end
rescue HTTPError
  tries ||= 0
  (tries += 1) < 10 ? retry : raise
end

#delete(key, options = {}) ⇒ Object

Delete the key from the store and return the current value

Parameters:

  • key (Object)
  • options (Hash) (defaults to: {})

Options Hash (options):

  • :raw (Boolean)

    Raw access without value transformation (See Transformer)

  • :prefix (String)

    Prefix key (See Transformer)

  • Other (Object)

    options as defined by the adapters or middleware

  • :batch (Boolean) — default: false

    Whether to do a href="https://docs.couchdb.org/en/stable/api/database/common.html#api-doc-batch-writes">docs.couchdb.org/en/stable/api/database/common.html#api-doc-batch-writes

    batch mode write
    
  • :full_commit (Boolean) — default: nil

    Set to `true` or `false` to override the server's href="https://docs.couchdb.org/en/stable/config/couchdb.html#couchdb/delayed_commits">docs.couchdb.org/en/stable/config/couchdb.html#couchdb/delayed_commits

    commit policy
    

Returns:

  • (Object)

    current value


116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/moneta/adapters/couch.rb', line 116

def delete(key, options = {})
  get_response = get(key, returns: :response)
  if get_response.success?
    value = body_to_value(get_response.body)
    existing_rev = parse_rev(get_response)
    query = { rev: existing_rev }
    query[:batch] = 'ok' if options[:batch]
    request(:delete, key,
            headers: full_commit_header(options[:full_commit]),
            query: query,
            expect: options[:batch] ? 202 : 200)
    delete_cached_rev(key)
    value
  end
rescue HTTPError
  tries ||= 0
  (tries += 1) < 10 ? retry : raise
end

#each_keyEnumerator #each_key {|key| ... } ⇒ self

Note:

Not every Moneta store implements this method, a NotImplementedError is raised if it is not supported.

Calls block once for each key in store, passing the key as a parameter. If no block is given, an enumerator is returned instead.

Overloads:

  • #each_keyEnumerator

    Returns An all-the-keys enumerator.

    Returns:

    • (Enumerator)

      An all-the-keys enumerator

  • #each_key {|key| ... } ⇒ self

    Yield Parameters:

    • key (Object)

      Each key is yielded to the supplied block

    Returns:

    • (self)

192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/moneta/adapters/couch.rb', line 192

def each_key
  return enum_for(:each_key) unless block_given?

  skip = 0
  limit = 1000
  loop do
    docs = all_docs(limit: limit, skip: skip)
    break if docs['rows'].empty?
    skip += docs['rows'].length
    docs['rows'].each do |row|
      key = row['id']
      @rev_cache[key] = row['value']['rev']
      yield key
    end
  end
  self
end

#key?(key, options = {}) ⇒ Boolean

Exists the value with key

Parameters:

  • key (Object)
  • options (Hash) (defaults to: {})

Options Hash (options):

  • :expires (Integer)

    Update expiration time (See Expires)

  • :prefix (String)

    Prefix key (See Transformer)

  • Other (Object)

    options as defined by the adapters or middleware

  • :cache_rev (Boolean) — default: true

    Whether to cache the rev of the document for faster updating

Returns:

  • (Boolean)

74
75
76
77
# File 'lib/moneta/adapters/couch.rb', line 74

def key?(key, options = {})
  cache_rev = options[:cache_rev] != false
  head(key, cache_rev: cache_rev)
end

#load(key, options = {}) ⇒ Object

Fetch value with key. Return nil if the key doesn't exist

Parameters:

  • key (Object)
  • options (Hash) (defaults to: {})

Options Hash (options):

  • :expires (Integer)

    Update expiration time (See Expires)

  • :raw (Boolean)

    Raw access without value transformation (See Transformer)

  • :prefix (String)

    Prefix key (See Transformer)

  • :sync (Boolean)

    Synchronized load (Cache reloads from adapter, Daybreak syncs with file)

  • Other (Object)

    options as defined by the adapters or middleware

  • :expires (Integer)

    Update expiration time (See Expires)

  • :prefix (String)

    Prefix key (See Transformer)

  • Other (Object)

    options as defined by the adapters or middleware

  • :cache_rev (Boolean) — default: true

    Whether to cache the rev of the document for faster updating

Returns:

  • (Object)

    value


81
82
83
84
85
# File 'lib/moneta/adapters/couch.rb', line 81

def load(key, options = {})
  cache_rev = options[:cache_rev] != false
  doc = get(key, cache_rev: cache_rev)
  doc ? doc_to_value(doc) : nil
end

#merge!(pairs, options = {}) ⇒ self #merge!(pairs, options = {}) {|key, old_value, new_value| ... } ⇒ self

Note:

Some adapters may implement this method atomically, or in two passes when a block is provided. The default implmentation uses #key?, #load and #store.

Stores the pairs in the key-value store, and returns itself. When a block is provided, it will be called before overwriting any existing values with the key, old value and supplied value, and the return value of the block will be used in place of the supplied value.

Overloads:

  • #merge!(pairs, options = {}) ⇒ self

    Parameters:

    • pairs (<(Object, Object)>)

      A collection of key-value pairs to store

    • options (Hash) (defaults to: {})

    Returns:

    • (self)
  • #merge!(pairs, options = {}) {|key, old_value, new_value| ... } ⇒ self

    Parameters:

    • pairs (<(Object, Object)>)

      A collection of key-value pairs to store

    • options (Hash) (defaults to: {})

    Yield Parameters:

    • key (Object)

      A key that whose value is being overwritten

    • old_value (Object)

      The existing value which is being overwritten

    • new_value (Object)

      The value supplied in the method call

    Yield Returns:

    • (Object)

      The value to use for overwriting

    Returns:

    • (self)

Parameters:

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :full_commit (Boolean) — default: nil

    Set to `true` or `false` to override the server's href="https://docs.couchdb.org/en/stable/config/couchdb.html#couchdb/delayed_commits">docs.couchdb.org/en/stable/config/couchdb.html#couchdb/delayed_commits

    commit policy
    

230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/moneta/adapters/couch.rb', line 230

def merge!(pairs, options = {})
  keys = pairs.map { |key, _| key }.to_a
  cache_revs(*keys.reject { |key| @rev_cache[key] })

  if block_given?
    existing = Hash[slice(*keys, **options)]
    pairs = pairs.map do |key, new_value|
      [
        key,
        if existing.key?(key)
          yield(key, existing[key], new_value)
        else
          new_value
        end
      ]
    end
  end

  docs = pairs.map { |key, value| value_to_doc(value, @rev_cache[key], key) }.to_a
  results = bulk_docs(docs, full_commit: options[:full_commit], returns: :doc)
  retries = results.each_with_object([]) do |row, retries|
    ok, id = row.values_at('ok', 'id')
    if ok
      @rev_cache[id] = row['rev']
    elsif row['error'] == 'conflict'
      delete_cached_rev(id)
      retries << pairs.find { |key,| key == id }
    else
      raise "Unrecognised response: #{row}"
    end
  end

  # Recursive call with all conflicts
  if retries.empty?
    self
  else
    merge!(retries, options)
  end
end

#slice(*keys, **options) ⇒ <(Object, Object)>

Note:

The keys in the return value may be the same objects that were supplied (i.e. Object#equal?), or may simply be equal (i.e. Object#==).

Note:

Some adapters may implement this method atomically. The default implmentation uses #values_at.

Returns a collection of key-value pairs corresponding to those supplied keys which are present in the key-value store, and their associated values. Only those keys present in the store will have pairs in the return value. The return value can be any enumerable object that yields pairs, so it could be a hash, but needn't be.

Parameters:

  • keys (<Object>)

    The keys for the values to fetch

  • options (Hash)

Options Hash (**options):

  • :expires (Integer)

    Update expiration time (See Expires)

  • :raw (Boolean)

    Raw access without value transformation (See Transformer)

  • :prefix (String)

    Prefix key (See Transformer)

  • :sync (Boolean)

    Synchronized load (Cache reloads from adapter, Daybreak syncs with file)

  • Other (Object)

    options as defined by the adapters or middleware

Returns:

  • (<(Object, Object)>)

    A collection of key-value pairs


217
218
219
220
221
222
223
# File 'lib/moneta/adapters/couch.rb', line 217

def slice(*keys, **options)
  docs = all_docs(keys: keys, include_docs: true)
  docs["rows"].map do |row|
    next unless doc = row['doc']
    [row['id'], doc_to_value(doc)]
  end.compact
end

#store(key, value, options = {}) ⇒ Object

Store value with key

Parameters:

  • key (Object)
  • value (Object)
  • options (Hash) (defaults to: {})

Options Hash (options):

  • :expires (Integer)

    Set expiration time (See Expires)

  • :raw (Boolean)

    Raw access without value transformation (See Transformer)

  • :prefix (String)

    Prefix key (See Transformer)

  • Other (Object)

    options as defined by the adapters or middleware

  • :batch (Boolean) — default: false

    Whether to do a href="https://docs.couchdb.org/en/stable/api/database/common.html#api-doc-batch-writes">docs.couchdb.org/en/stable/api/database/common.html#api-doc-batch-writes

    batch mode write
    
  • :full_commit (Boolean) — default: nil

    Set to `true` or `false` to override the server's href="https://docs.couchdb.org/en/stable/config/couchdb.html#couchdb/delayed_commits">docs.couchdb.org/en/stable/config/couchdb.html#couchdb/delayed_commits

    commit policy
    
  • :expires (Integer)

    Update expiration time (See Expires)

  • :prefix (String)

    Prefix key (See Transformer)

  • Other (Object)

    options as defined by the adapters or middleware

  • :cache_rev (Boolean) — default: true

    Whether to cache the rev of the document for faster updating

Returns:

  • value


96
97
98
99
100
101
102
103
104
105
106
# File 'lib/moneta/adapters/couch.rb', line 96

def store(key, value, options = {})
  put(key, value_to_doc(value, rev(key)),
      headers: full_commit_header(options[:full_commit]),
      query: options[:batch] ? { batch: 'ok' } : {},
      cache_rev: options[:cache_rev] != false,
      expect: options[:batch] ? 202 : 201)
  value
rescue HTTPError
  tries ||= 0
  (tries += 1) < 10 ? retry : raise
end

#values_at(*keys, **options) ⇒ Array<Object, nil>

Note:

Some adapters may implement this method atomically, but the default implementation simply makes repeated calls to #load.

Returns an array containing the values associated with the given keys, in the same order as the supplied keys. If a key is not present in the key-value-store, nil is returned in its place.

Parameters:

  • keys (<Object>)

    The keys for the values to fetch

  • options (Hash)

Options Hash (**options):

  • :expires (Integer)

    Update expiration time (See Expires)

  • :raw (Boolean)

    Raw access without value transformation (See Transformer)

  • :prefix (String)

    Prefix key (See Transformer)

  • :sync (Boolean)

    Synchronized load (Cache reloads from adapter, Daybreak syncs with file)

  • Other (Object)

    options as defined by the adapters or middleware

Returns:

  • (Array<Object, nil>)

    Array containing the values requested, with nil for missing values


211
212
213
214
# File 'lib/moneta/adapters/couch.rb', line 211

def values_at(*keys, **options)
  hash = Hash[slice(*keys, **options)]
  keys.map { |key| hash[key] }
end