dolly 1.1.7 → 3.0.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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -0
  3. data/lib/dolly.rb +1 -23
  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 +26 -69
  8. data/lib/dolly/configuration.rb +35 -10
  9. data/lib/dolly/connection.rb +91 -22
  10. data/lib/dolly/depracated_database.rb +24 -0
  11. data/lib/dolly/document.rb +32 -206
  12. data/lib/dolly/document_creation.rb +20 -0
  13. data/lib/dolly/document_state.rb +65 -0
  14. data/lib/dolly/document_type.rb +28 -0
  15. data/lib/dolly/exceptions.rb +21 -0
  16. data/lib/dolly/identity_properties.rb +29 -0
  17. data/lib/dolly/properties.rb +31 -0
  18. data/lib/dolly/property.rb +58 -47
  19. data/lib/dolly/property_manager.rb +47 -0
  20. data/lib/dolly/property_set.rb +18 -0
  21. data/lib/dolly/query.rb +39 -67
  22. data/lib/dolly/query_arguments.rb +35 -0
  23. data/lib/dolly/request.rb +12 -107
  24. data/lib/dolly/request_header.rb +26 -0
  25. data/lib/dolly/timestamp.rb +24 -0
  26. data/lib/dolly/version.rb +1 -1
  27. data/lib/{dolly → railties}/railtie.rb +2 -1
  28. data/lib/refinements/string_refinements.rb +28 -0
  29. data/lib/tasks/db.rake +4 -3
  30. data/test/bulk_document_test.rb +8 -5
  31. data/test/document_test.rb +137 -53
  32. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  33. data/test/dummy/log/test.log +46417 -46858
  34. data/test/test_helper.rb +14 -20
  35. metadata +42 -145
  36. data/Rakefile +0 -11
  37. data/lib/dolly/bulk_error.rb +0 -16
  38. data/lib/dolly/db_config.rb +0 -20
  39. data/lib/dolly/interpreter.rb +0 -5
  40. data/lib/dolly/logger.rb +0 -9
  41. data/lib/dolly/name_space.rb +0 -28
  42. data/lib/dolly/timestamps.rb +0 -21
  43. data/lib/exceptions/dolly.rb +0 -47
  44. data/test/collection_test.rb +0 -59
  45. data/test/configuration_test.rb +0 -9
  46. data/test/dummy/README.rdoc +0 -28
  47. data/test/dummy/Rakefile +0 -6
  48. data/test/dummy/app/assets/javascripts/application.js +0 -13
  49. data/test/dummy/app/assets/stylesheets/application.css +0 -13
  50. data/test/dummy/app/controllers/application_controller.rb +0 -5
  51. data/test/dummy/app/helpers/application_helper.rb +0 -2
  52. data/test/dummy/app/views/layouts/application.html.erb +0 -14
  53. data/test/dummy/bin/bundle +0 -3
  54. data/test/dummy/bin/rails +0 -4
  55. data/test/dummy/bin/rake +0 -4
  56. data/test/dummy/config.ru +0 -4
  57. data/test/dummy/config/application.rb +0 -27
  58. data/test/dummy/config/boot.rb +0 -5
  59. data/test/dummy/config/couchdb.yml +0 -13
  60. data/test/dummy/config/environment.rb +0 -5
  61. data/test/dummy/config/environments/development.rb +0 -29
  62. data/test/dummy/config/environments/production.rb +0 -80
  63. data/test/dummy/config/environments/test.rb +0 -36
  64. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
  65. data/test/dummy/config/initializers/inflections.rb +0 -16
  66. data/test/dummy/config/initializers/mime_types.rb +0 -5
  67. data/test/dummy/config/initializers/secret_token.rb +0 -12
  68. data/test/dummy/config/initializers/session_store.rb +0 -3
  69. data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
  70. data/test/dummy/config/locales/en.yml +0 -23
  71. data/test/dummy/config/routes.rb +0 -56
  72. data/test/dummy/lib/couch_rest_adapter/railtie.rb +0 -10
  73. data/test/dummy/public/404.html +0 -58
  74. data/test/dummy/public/422.html +0 -58
  75. data/test/dummy/public/500.html +0 -57
  76. data/test/dummy/public/favicon.ico +0 -0
  77. data/test/factories/factories.rb +0 -8
  78. data/test/request_test.rb +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 45fa7180b3da598f09d522101ae72b4d35bff7d1
4
- data.tar.gz: 3eceb48ee307b83c6b379e2bdacd7530cafbe0f5
3
+ metadata.gz: c26d50611f8efd7f5cd7b973256ddb6d885a747a
4
+ data.tar.gz: 4c16a1b2b21b55eeba0a9b7c0307dce20af3be90
5
5
  SHA512:
6
- metadata.gz: 2e3e721bce03f02a766d159950956ac4dff0394cf437f1fc06117ad5755ee9a0e8da0baa004c2c247710c9909fe36a5be2815ea78eb5c4371700e9376ea1b9e1
7
- data.tar.gz: 3215f948add4eb9b8f7048bafcd0a98e01c91ce09c4c4601a0c58d4a9863f1d2aa96a88de65f62d70e32945493bc82c7b9d43a25726457308321a1022e3c65f5
6
+ metadata.gz: a4a534952347905e024d8da5eec35a31220305edb10528e2d8e30608691ab42143a09c08a005f0121c6acda6a7f6c53320907eb9c81f4b821966d1980c6040d8
7
+ data.tar.gz: b4f1d1f5994aa3d963d2fe77f2c8e4b67e4d657c66a4da5548924630da64d28a70f0902b9bd503c08c4a982a47a34e188a0468ee2559eab8a0c1599ede6881c1
@@ -0,0 +1,35 @@
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.
@@ -1,28 +1,6 @@
1
1
  require "dolly/version"
2
2
  require "dolly/document"
3
- require "dolly/configuration"
4
- require 'dolly/railtie' if defined?(Rails)
3
+ require 'railties/railtie' if defined?(Rails)
5
4
 
6
5
  module Dolly
7
- class << self
8
- def configure
9
- yield config
10
- end
11
-
12
- def config
13
- @config ||= Configuration.new
14
- end
15
-
16
- def reset!
17
- @config = Configuration.new
18
- end
19
-
20
- def log_requests?
21
- !!config.log_requests
22
- end
23
-
24
- def logger
25
- @logger ||= config.logger
26
- end
27
- end
28
6
  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 #{doc['id']} 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,45 @@
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 :info
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: [], **info)
6
+ @info = info
7
+ #TODO: We should raise an exception if one of the
8
+ # requested documents is missing
9
+ super 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_type(row[:id])
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_type(key)
32
+ key.match(%r{^([^/]+)/})[1].split('_').collect(&:capitalize).join
62
33
  end
63
34
 
64
- def to_json options = {}
65
- load if empty?
66
- map{|r| r.doc }.to_json(options)
35
+ def collectable_row?(row)
36
+ !deleted_doc?(row) && row[:error].nil?
67
37
  end
68
38
 
69
- private
70
- def docs_class
71
- @docs_class
72
- end
73
-
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
39
+ def deleted_doc?(row)
40
+ value = row&.fetch(:value, {})
41
+ return false unless value.is_a? Hash
42
+ value.fetch(:deleted, false)
81
43
  end
82
-
83
- def json
84
- @json
85
- end
86
-
87
44
  end
88
45
  end
@@ -1,18 +1,43 @@
1
- require 'dolly/logger'
1
+ # frozen_string_literal: true
2
+ require 'erb'
2
3
 
3
4
  module Dolly
4
- class Configuration
5
- attr_accessor :log_requests, :log_path, :log
5
+ module Configuration
6
+ attr_writer :config_file
6
7
 
7
- def initialize
8
- @log_requests = false
9
- @log_path = $stdout
10
- @log = :dolly
8
+ def env
9
+ @env ||= configuration[db.to_s]
11
10
  end
12
11
 
13
- def logger
14
- return Rails.logger if log.to_sym == :rails
15
- Dolly::Logger.new self
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')
16
41
  end
17
42
  end
18
43
  end
@@ -1,42 +1,111 @@
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' }
12
15
 
13
- def database
14
- @database ||= Request.new(env)
16
+ using StringRefinements
17
+
18
+ def initialize db = :default, app_env = :development
19
+ @db = db
20
+ @app_env = app_env
21
+ end
22
+
23
+ def get(resource, data = {})
24
+ query = { query: values_to_json(data) } if data
25
+ request :get, resource.cgi_escape, query
26
+ end
27
+
28
+ def post resource, data
29
+ request :post, resource.cgi_escape, data
30
+ end
31
+
32
+ def put resource, data
33
+ request :put, resource.cgi_escape, data
15
34
  end
16
35
 
17
- def bulk_document
18
- @bulk_document ||= BulkDocument.new(database)
36
+ def delete resource, rev
37
+ request :delete, resource.cgi_escape, query: { rev: rev }
19
38
  end
20
39
 
21
- def bulk_save
22
- bulk_document.save
40
+ def view resource, opts
41
+ request :get, resource, query: values_to_json({include_docs: true}.merge!(opts))
23
42
  end
24
43
 
25
- def database_name value
26
- @@database_name ||= value
44
+ def attach resource, attachment_name, data, headers = {}
45
+ request :put, "#{resource.cgi_escape}/#{attachment_name}", { _body: data }.merge(headers: headers)
27
46
  end
28
47
 
29
- def default_doc
30
- "#{design_doc}/_view/find"
48
+ def uuids opts = {}
49
+ tools("_uuids", opts)[:uuids]
31
50
  end
32
51
 
33
- def design_doc
34
- "_design/#{env["design"]}"
52
+ def stats
53
+ get("/#{db_name}")
35
54
  end
36
55
 
37
- def next_id
38
- namespace database.uuids.first
56
+ def tools path, opts = nil
57
+ request(:get, "/#{path}", opts)
58
+ end
59
+
60
+ def request(method, resource, data = {})
61
+ headers = Dolly::HeaderRequest.new data.delete(:headers)
62
+ uri = build_uri(resource, data.delete(:query))
63
+ klass = request_method(method)
64
+ req = klass.new(uri, headers)
65
+ req.body = format_data(data, headers.json?)
66
+ response = start_request(req)
67
+
68
+ response_format(response)
69
+ end
70
+
71
+ private
72
+
73
+ def start_request(req)
74
+ Net::HTTP.start(req.uri.hostname, req.uri.port) do |http|
75
+ req.basic_auth env['username'], env['password'] if env['username'].present?
76
+ http.request(req)
77
+ end
39
78
  end
40
79
 
80
+ def response_format(res)
81
+ raise Dolly::ResourceNotFound if res.code.to_i == 404
82
+ raise Dolly::ServerError.new(res.body) if (400..600).include? res.code.to_i
83
+ Oj.load(res.body, symbol_keys: true)
84
+ end
85
+
86
+ def format_data(data = nil, is_json)
87
+ return unless data
88
+ body = data.delete(:_body) || data
89
+ is_json ? body.to_json : body
90
+ end
91
+
92
+ def build_uri(resource, query = nil)
93
+ query_str = "?#{to_query(query)}" if query
94
+ uri = (resource =~ %r{^/}) ? resource : "/#{db_name}/#{resource}"
95
+
96
+ URI("#{base_uri}#{uri}#{query_str}")
97
+ end
98
+
99
+ def request_method(method_name)
100
+ Object.const_get("Net::HTTP::#{method_name.capitalize}")
101
+ end
102
+
103
+ def values_to_json hash
104
+ hash.each_with_object({}) { |(k,v), h| h[k] = v.is_a?(Numeric) ? v : v.to_json }
105
+ end
106
+
107
+ def to_query(string)
108
+ string.map { |k, v| "#{k}=#{v}" }.sort * '&'
109
+ end
41
110
  end
42
111
  end