dolly 1.1.4 → 3.0.1

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.
Files changed (84) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +78 -0
  3. data/lib/dolly.rb +4 -2
  4. data/lib/dolly/attachment.rb +29 -0
  5. data/lib/dolly/bulk_document.rb +27 -26
  6. data/lib/dolly/class_methods_delegation.rb +15 -0
  7. data/lib/dolly/collection.rb +32 -65
  8. data/lib/dolly/configuration.rb +43 -0
  9. data/lib/dolly/connection.rb +101 -22
  10. data/lib/dolly/depracated_database.rb +24 -0
  11. data/lib/dolly/document.rb +48 -198
  12. data/lib/dolly/document_creation.rb +20 -0
  13. data/lib/dolly/document_state.rb +66 -0
  14. data/lib/dolly/document_type.rb +47 -0
  15. data/lib/dolly/exceptions.rb +32 -0
  16. data/lib/dolly/identity_properties.rb +29 -0
  17. data/lib/dolly/mango.rb +124 -0
  18. data/lib/dolly/mango_index.rb +65 -0
  19. data/lib/dolly/properties.rb +36 -0
  20. data/lib/dolly/property.rb +58 -47
  21. data/lib/dolly/property_manager.rb +47 -0
  22. data/lib/dolly/property_set.rb +23 -0
  23. data/lib/dolly/query.rb +37 -67
  24. data/lib/dolly/query_arguments.rb +35 -0
  25. data/lib/dolly/request.rb +12 -94
  26. data/lib/dolly/request_header.rb +26 -0
  27. data/lib/dolly/timestamp.rb +24 -0
  28. data/lib/dolly/version.rb +1 -1
  29. data/lib/dolly/view_query.rb +14 -0
  30. data/lib/{dolly → railties}/railtie.rb +2 -1
  31. data/lib/refinements/hash_refinements.rb +27 -0
  32. data/lib/refinements/string_refinements.rb +28 -0
  33. data/lib/tasks/db.rake +20 -4
  34. data/test/bulk_document_test.rb +8 -5
  35. data/test/document_test.rb +132 -95
  36. data/test/document_type_test.rb +28 -0
  37. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  38. data/test/dummy/log/test.log +111132 -0
  39. data/test/inheritance_test.rb +23 -0
  40. data/test/mango_index_test.rb +64 -0
  41. data/test/mango_test.rb +273 -0
  42. data/test/test_helper.rb +63 -18
  43. data/test/view_query_test.rb +27 -0
  44. metadata +57 -141
  45. data/Rakefile +0 -11
  46. data/lib/dolly/bulk_error.rb +0 -16
  47. data/lib/dolly/db_config.rb +0 -20
  48. data/lib/dolly/interpreter.rb +0 -5
  49. data/lib/dolly/name_space.rb +0 -28
  50. data/lib/dolly/timestamps.rb +0 -21
  51. data/lib/exceptions/dolly.rb +0 -38
  52. data/test/collection_test.rb +0 -59
  53. data/test/dummy/README.rdoc +0 -28
  54. data/test/dummy/Rakefile +0 -6
  55. data/test/dummy/app/assets/javascripts/application.js +0 -13
  56. data/test/dummy/app/assets/stylesheets/application.css +0 -13
  57. data/test/dummy/app/controllers/application_controller.rb +0 -5
  58. data/test/dummy/app/helpers/application_helper.rb +0 -2
  59. data/test/dummy/app/views/layouts/application.html.erb +0 -14
  60. data/test/dummy/bin/bundle +0 -3
  61. data/test/dummy/bin/rails +0 -4
  62. data/test/dummy/bin/rake +0 -4
  63. data/test/dummy/config.ru +0 -4
  64. data/test/dummy/config/application.rb +0 -27
  65. data/test/dummy/config/boot.rb +0 -5
  66. data/test/dummy/config/couchdb.yml +0 -13
  67. data/test/dummy/config/environment.rb +0 -5
  68. data/test/dummy/config/environments/development.rb +0 -29
  69. data/test/dummy/config/environments/production.rb +0 -80
  70. data/test/dummy/config/environments/test.rb +0 -36
  71. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
  72. data/test/dummy/config/initializers/inflections.rb +0 -16
  73. data/test/dummy/config/initializers/mime_types.rb +0 -5
  74. data/test/dummy/config/initializers/secret_token.rb +0 -12
  75. data/test/dummy/config/initializers/session_store.rb +0 -3
  76. data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
  77. data/test/dummy/config/locales/en.yml +0 -23
  78. data/test/dummy/config/routes.rb +0 -56
  79. data/test/dummy/lib/couch_rest_adapter/railtie.rb +0 -10
  80. data/test/dummy/public/404.html +0 -58
  81. data/test/dummy/public/422.html +0 -58
  82. data/test/dummy/public/500.html +0 -57
  83. data/test/dummy/public/favicon.ico +0 -0
  84. data/test/factories/factories.rb +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c296071100cabab1fada9107b27de3e3f9af84a2
4
- data.tar.gz: 29f8e5081e455621370bd73efe9a3f9d6551cdaf
2
+ SHA256:
3
+ metadata.gz: 52fcc7a3f0888239aaff1a6398a81112d031722db14d3070d83d574179414197
4
+ data.tar.gz: 20d5efb134448842c4c547d85f0f395993fdd8817898e7b174e3e5f5714a7022
5
5
  SHA512:
6
- metadata.gz: 0f2d6d51779aa4596c733c1c60ccd1151a2a31c4eb120ec80614566bec61156f72ac7cbdbc5df9ae1edb95b3e4d6d3ef9e97990b178d694b0732f3d8be6527f5
7
- data.tar.gz: 7ac04a9806361c649b39bf9f7749db23a9c2ee50d5901a2dff3a1b45578e145477537bad44265331ecdc90151952cc5cbc551582d6df888c94c2d08d659246c6
6
+ metadata.gz: f684874eaa87448a0ae67fddf5df98f3e86c5c6a53f8fb7b75e1331d16d63f07e6ef8eb2db17e461609433ccaf770a1c3a3c27b1551b5cf7c823f2d010005075
7
+ data.tar.gz: f44fa8ea168a1d6ca31042c4eed5b8cf0fdb1fd11dfcc5fb76e177598cce96512e6cca8a842e6c646fa4f65f8e4e0ea9759379ad73954c42fb1694c9803d42c3
@@ -0,0 +1,78 @@
1
+ # Dolly3
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/dolly3`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'dolly3'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install dolly3
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/dolly3.
36
+
37
+
38
+ ## Migrating from couch 1.x to 2.x
39
+
40
+ [Official docs](https://docs.couchdb.org/en/2.3.1/install/index.html)
41
+
42
+
43
+ You will need to uninstall the couchdb service with brew
44
+
45
+ `brew services stop couchdb`
46
+ `brew services uninstall couchdb`
47
+
48
+ Download the application from the following source
49
+
50
+ http://couchdb.apache.org/#download
51
+
52
+ launch fauxton and check your installation
53
+
54
+ Copy [this file](https://github.com/apache/couchdb/blob/master/rel/overlay/bin/couchup) into your filesystem
55
+
56
+
57
+ make it executable
58
+
59
+ `chmod +x couchup.py`
60
+
61
+ and run it
62
+
63
+ `./couchup.py -h`
64
+
65
+ You might need to install python 3 and pip3 and the following libs
66
+
67
+ `pip3 install requests progressbar2`
68
+
69
+ move your .couch files into the specified `database_dir` in your [fauxton config](http://127.0.0.1:5984/_utils/#_config/couchdb@localhost)
70
+
71
+
72
+ ```
73
+ $ ./couchup list # Shows your unmigrated 1.x databases
74
+ $ ./couchup replicate -a # Replicates your 1.x DBs to 2.x
75
+ $ ./couchup rebuild -a # Optional; starts rebuilding your views
76
+ $ ./couchup delete -a # Deletes your 1.x DBs (careful!)
77
+ $ ./couchup list # Should show no remaining databases!
78
+ ```
@@ -1,5 +1,7 @@
1
1
  require "dolly/version"
2
2
  require "dolly/document"
3
- require 'dolly/railtie' if defined?(Rails)
3
+ require 'dolly/mango_index'
4
+ require 'railties/railtie' if defined?(Rails)
4
5
 
5
- module Dolly; end
6
+ module Dolly
7
+ end
@@ -0,0 +1,29 @@
1
+ require 'base64'
2
+
3
+ module Dolly
4
+ module Attachment
5
+ def attach_file! file_name, mime_type, body, opts={}
6
+ attach_file file_name, mime_type, body, opts
7
+ save
8
+ end
9
+
10
+ def attach_file file_name, mime_type, body, opts={}
11
+ if opts[:inline]
12
+ attach_inline_file file_name, mime_type, body
13
+ else
14
+ attach_standalone_file file_name, mime_type, body
15
+ end
16
+ end
17
+
18
+ def attach_inline_file file_name, mime_type, body
19
+ attachment_data = { file_name.to_s => { 'content_type' => mime_type,
20
+ 'data' => Base64.encode64(body)} }
21
+ doc['_attachments'] ||= {}
22
+ doc['_attachments'].merge! attachment_data
23
+ end
24
+
25
+ def attach_standalone_file file_name, mime_type, body
26
+ self.class.connection.attach id_as_resource, CGI.escape(file_name), body, { 'Content-Type' => mime_type }
27
+ end
28
+ end
29
+ end
@@ -1,37 +1,34 @@
1
- require 'dolly/bulk_error'
2
-
3
1
  module Dolly
4
2
  class BulkDocument
5
- include Enumerable
6
3
  extend Forwardable
7
4
 
8
- DOC_NAME = "_bulk_docs".freeze
5
+ DOC_NAME = '_bulk_docs'
9
6
 
10
- attr_reader :payload, :database
7
+ attr_reader :payload, :connection
11
8
  attr_accessor :errors, :response
12
9
 
13
10
  def_delegators :docs, :[], :<<
14
11
 
15
- def initialize database, ary = []
16
- @database = database
12
+ def initialize(connection, ary = [])
13
+ @connection = connection
17
14
  @payload = Hash.new
18
- self.payload[:docs] = ary
15
+ payload[:docs] = ary
19
16
  end
20
17
 
21
18
  def docs
22
- self.payload[:docs]
19
+ payload[:docs]
23
20
  end
24
21
 
25
22
  def save
26
23
  return if docs.empty?
27
- self.response = JSON::parse self.database.post(DOC_NAME, json_payload)
24
+ self.response = connection.post(DOC_NAME, docs_payload)
28
25
  build_errors
29
26
  update_revs
30
27
  end
31
28
 
32
29
  def delete
33
30
  return if docs.empty?
34
- JSON::parse database.post DOC_NAME, json_payload("_deleted" => true)
31
+ connection.post DOC_NAME, json_payload(_deleted: true)
35
32
  end
36
33
 
37
34
  def clear
@@ -45,41 +42,45 @@ module Dolly
45
42
  end
46
43
 
47
44
  private
45
+
48
46
  def update_revs
49
- self.response.each do |doc|
50
- next if doc['error']
51
- item = self.payload[:docs].detect{|d| d.id == doc['id']}
47
+ response.each do |doc|
48
+ next if doc[:error]
49
+ item = payload[:docs].detect { |d| d.id == doc[:id] }
50
+
52
51
  if item.nil?
53
- self.errors << BulkError.new({"error" => "Document saved but not local rev updated.", "reason" => "Document with id #{doc['id']} on bulk doc was not found in payload.", "obj" => nil})
52
+ errors << response_error(item)
54
53
  next
55
54
  end
56
- item.doc['_rev'] = doc['rev']
57
- self.payload[:docs].delete item
55
+
56
+ item.rev = doc[:rev]
57
+ payload[:docs].delete(item)
58
58
  end
59
+
59
60
  clean_response
60
61
  end
61
62
 
62
63
  def clean_response
63
- self.response.delete_if {|doc| !doc['error'] }
64
+ response.delete_if { |d| !d[:error] }
64
65
  end
65
66
 
66
67
  def build_errors
67
68
  self.errors = response_errors.map do |err|
68
- obj = self.payload[:docs].detect{|d| d.id == err['id']} if err['id']
69
- BulkError.new err.merge!("obj" => obj)
69
+ obj = payload[:docs].detect { |d| d.id == err[:id]} if err[:id]
70
+ BulkError.new err.merge!(obj: obj)
70
71
  end
71
72
  end
72
73
 
73
- def bare_docs
74
- self.payload[:docs].map(&:doc)
74
+ def docs_payload opts = {}
75
+ { docs: docs.map {|d| d.to_h.merge(opts) } }
75
76
  end
76
77
 
77
- def json_payload opts = {}
78
- {docs: bare_docs.map{|d| d.merge(opts)} }.to_json
78
+ def response_errors
79
+ self.response.select{ |d| d[:error] }
79
80
  end
80
81
 
81
- def response_errors
82
- self.response.select{|d| d['error']}
82
+ def response_error(item)
83
+ BulkError.new(error: 'Document saved but not local rev updated.', reason: "Document with id #{item} on bulk doc was not found in payload.", obj: nil)
83
84
  end
84
85
  end
85
86
  end
@@ -0,0 +1,15 @@
1
+ module Dolly
2
+ module ClassMethodsDelegation
3
+ def connection
4
+ self.class.connection
5
+ end
6
+
7
+ def property_clean_doc(doc)
8
+ self.class.property_clean_doc(doc)
9
+ end
10
+
11
+ def database
12
+ self.class.database
13
+ end
14
+ end
15
+ end
@@ -1,88 +1,55 @@
1
1
  module Dolly
2
- class Collection < DelegateClass(Set)
3
- attr_accessor :rows
4
- attr_writer :json, :docs_class
2
+ class Collection < DelegateClass(Array)
3
+ attr_reader :options
5
4
 
6
- def initialize str, docs_class
7
- @docs_class = docs_class
8
- @json = str
9
- initial = []
10
- super(initial)
11
- load
5
+ def initialize(rows: [], options: {})
6
+ @options = options
7
+ #TODO: We should raise an exception if one of the
8
+ # requested documents is missing
9
+ super rows[:rows].map(&collect_docs).compact
12
10
  end
13
11
 
14
- def last
15
- to_a.last
12
+ def first_or_all(forced_first = false)
13
+ return self if forced_first
14
+ single? ? first : self
16
15
  end
17
16
 
18
- def update_properties! properties ={}
19
- properties.each do |key, value|
20
-
21
- regex = %r{
22
- \"#{key}\": # find key definition in json string
23
- ( # start value group
24
- \"[^\"]*\" # find anything (even empty) between \" and \"
25
- | # logical OR
26
- null #literal null value
27
- ) # end value group
28
- }x
29
-
30
- raise Dolly::MissingPropertyError unless json.match regex
31
- json.gsub! regex, "\"#{key}\":\"#{value}\""
32
- end
33
-
34
- BulkDocument.new(Dolly::Document.database, to_a).save
35
- clear
36
- load
37
- self
17
+ def single?
18
+ size <= 1
38
19
  end
39
20
 
40
- def each &block
41
- load if empty?
42
- super &block
43
- #TODO: returning nil to avoid extra time serializing set.
44
- nil
45
- end
21
+ private
46
22
 
47
- def rows= ary
48
- ary.each do |r|
49
- next unless r['doc']
50
- properties = r['doc']
51
- id = properties.delete '_id'
52
- rev = properties.delete '_rev' if properties['_rev']
53
- document = (docs_class || doc_class(id)).new properties
54
- document.doc = properties.merge({'_id' => id, '_rev' => rev})
55
- self << document
23
+ def collect_docs
24
+ lambda do |row|
25
+ next unless collectable_row?(row)
26
+ klass = Object.const_get(doc_model(row))
27
+ klass.from_doc(row[:doc])
56
28
  end
57
29
  end
58
30
 
59
- def load
60
- parsed = JSON::parse json
61
- self.rows = parsed['rows']
31
+ def doc_model(doc)
32
+ options[:doc_type] || constantize_key(doc[:doc_type]) || constantize_key(doc_type_for(doc[:id]))
62
33
  end
63
34
 
64
- def to_json options = {}
65
- load if empty?
66
- map{|r| r.doc }.to_json(options)
35
+ def doc_type_for(key)
36
+ return false if key.nil?
37
+ key.match(%r{^([^/]+)/})[1]
67
38
  end
68
39
 
69
- private
70
- def docs_class
71
- @docs_class
40
+ def constantize_key(key)
41
+ return false if key.nil?
42
+ key.split('_').collect(&:capitalize).join
72
43
  end
73
44
 
74
- def doc_class id
75
- # TODO: We need to improve and document the way we return
76
- # multiple types when querying from a class, as it might
77
- # be confusing. We *could* also get dolly to parse the result
78
- # before sending it back to the client.
79
- doc_class = id[/^[a-z_]+/].camelize.constantize
80
- docs_class == doc_class ? docs_class : doc_class
45
+ def collectable_row?(row)
46
+ !deleted_doc?(row) && row[:error].nil?
81
47
  end
82
48
 
83
- def json
84
- @json
49
+ def deleted_doc?(row)
50
+ value = row&.fetch(:value, {})
51
+ return false unless value.is_a? Hash
52
+ value.fetch(:deleted, false)
85
53
  end
86
-
87
54
  end
88
55
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+ require 'erb'
3
+
4
+ module Dolly
5
+ module Configuration
6
+ attr_writer :config_file
7
+
8
+ def env
9
+ @env ||= configuration[db.to_s]
10
+ end
11
+
12
+ def base_uri
13
+ "#{protocol}#{host}#{port}"
14
+ end
15
+
16
+ def protocol
17
+ "#{env['protocol']}://"
18
+ end
19
+
20
+ def host
21
+ env['host']
22
+ end
23
+
24
+ def port
25
+ return unless env['port']
26
+ ":#{env['port']}"
27
+ end
28
+
29
+ def db_name
30
+ env['name']
31
+ end
32
+
33
+ def configuration
34
+ @config_data ||= File.read(config_file)
35
+ raise Dolly::InvalidConfigFileError if @config_data&.empty?
36
+ YAML::load(ERB.new(@config_data).result)[app_env.to_s]
37
+ end
38
+
39
+ def config_file
40
+ @config_file ||= File.join('config', 'couchdb.yml')
41
+ end
42
+ end
43
+ end
@@ -1,42 +1,121 @@
1
- require "dolly/request"
2
- require "dolly/name_space"
3
- require "dolly/db_config"
4
- require "dolly/bulk_document"
1
+ require 'oj'
2
+ require 'cgi'
3
+ require 'net/http'
4
+ require 'dolly/request_header'
5
+ require 'dolly/exceptions'
6
+ require 'dolly/configuration'
7
+ require 'refinements/string_refinements'
5
8
 
6
9
  module Dolly
7
- module Connection
8
- include Dolly::NameSpace
9
- include Dolly::DbConfig
10
+ class Connection
11
+ include Dolly::Configuration
12
+ attr_reader :db, :app_env
10
13
 
11
- @@design_doc = nil
14
+ DEFAULT_HEADER = { 'Content-Type' => 'application/json' }
15
+ SECURE_PROTOCOL = 'https'
12
16
 
13
- def database
14
- @database ||= Request.new(env)
17
+ using StringRefinements
18
+
19
+ def initialize db = :default, app_env = :development
20
+ @db = db
21
+ @app_env = app_env
22
+ end
23
+
24
+ def get(resource, data = {})
25
+ query = { query: values_to_json(data) } if data
26
+ request :get, resource.cgi_escape, query
27
+ end
28
+
29
+ def post resource, data
30
+ request :post, resource.cgi_escape, data
31
+ end
32
+
33
+ def put resource, data
34
+ request :put, resource.cgi_escape, data
15
35
  end
16
36
 
17
- def bulk_document
18
- @bulk_document ||= BulkDocument.new(database)
37
+ def delete resource, rev = nil, escape: true
38
+ query = { query: { rev: rev } } if rev
39
+ resource = resource.cgi_escape if escape
40
+ request :delete, resource, query
19
41
  end
20
42
 
21
- def bulk_save
22
- bulk_document.save
43
+ def view resource, opts
44
+ request :get, resource, query: values_to_json({include_docs: true}.merge!(opts))
23
45
  end
24
46
 
25
- def database_name value
26
- @@database_name ||= value
47
+ def attach resource, attachment_name, data, headers = {}
48
+ request :put, "#{resource.cgi_escape}/#{attachment_name}", { _body: data }.merge(headers: headers)
27
49
  end
28
50
 
29
- def default_doc
30
- "#{design_doc}/_view/find"
51
+ def uuids opts = {}
52
+ tools("_uuids", opts)[:uuids]
31
53
  end
32
54
 
33
- def design_doc
34
- "_design/#{env["design"]}"
55
+ def stats
56
+ get("/#{db_name}")
35
57
  end
36
58
 
37
- def next_id
38
- namespace database.uuids.first
59
+ def tools path, opts = nil
60
+ request(:get, "/#{path}", opts)
61
+ end
62
+
63
+ def request(method, resource, data = {})
64
+ headers = Dolly::HeaderRequest.new data&.delete(:headers)
65
+ uri = build_uri(resource, data&.delete(:query))
66
+ klass = request_method(method)
67
+ req = klass.new(uri, headers)
68
+ req.body = format_data(data, headers.json?)
69
+ response = start_request(req)
70
+
71
+ response_format(response, method)
72
+ end
73
+
74
+ private
75
+
76
+ def start_request(req)
77
+ req.basic_auth env['username'], env['password'] if env['username']&.present?
78
+
79
+ http = Net::HTTP.new(req.uri.host, req.uri.port)
80
+ http.use_ssl = secure?
81
+
82
+ http.request(req)
39
83
  end
40
84
 
85
+ def secure?
86
+ env['protocol'] == SECURE_PROTOCOL
87
+ end
88
+
89
+ def response_format(res, method)
90
+ raise Dolly::ResourceNotFound if res.code.to_i == 404
91
+ raise Dolly::ServerError.new(res.body) if (400..600).include? res.code.to_i
92
+ return res if method == :head
93
+ Oj.load(res.body, symbol_keys: true)
94
+ end
95
+
96
+ def format_data(data = nil, is_json)
97
+ return unless data
98
+ body = data.delete(:_body) || data
99
+ is_json ? body.to_json : body
100
+ end
101
+
102
+ def build_uri(resource, query = nil)
103
+ query_str = "?#{to_query(query)}" if query
104
+ uri = (resource =~ %r{^/}) ? resource : "/#{db_name}/#{resource}"
105
+
106
+ URI("#{base_uri}#{uri}#{query_str}")
107
+ end
108
+
109
+ def request_method(method_name)
110
+ Object.const_get("Net::HTTP::#{method_name.capitalize}")
111
+ end
112
+
113
+ def values_to_json hash
114
+ hash.each_with_object({}) { |(k,v), h| h[k] = v.is_a?(Numeric) ? v : v.to_json }
115
+ end
116
+
117
+ def to_query(string)
118
+ string.map { |k, v| "#{k}=#{v}" }.sort * '&'
119
+ end
41
120
  end
42
121
  end