dolly 1.1.5 → 3.1.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 (87) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +78 -0
  3. data/lib/dolly.rb +2 -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 +32 -65
  8. data/lib/dolly/configuration.rb +35 -10
  9. data/lib/dolly/connection.rb +86 -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 +156 -0
  18. data/lib/dolly/mango_index.rb +73 -0
  19. data/lib/dolly/properties.rb +36 -0
  20. data/lib/dolly/property.rb +58 -47
  21. data/lib/dolly/property_manager.rb +53 -0
  22. data/lib/dolly/property_set.rb +23 -0
  23. data/lib/dolly/query.rb +63 -75
  24. data/lib/dolly/query_arguments.rb +35 -0
  25. data/lib/dolly/request.rb +12 -105
  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 +21 -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 +27 -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 +55435 -33484
  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/property_manager_test.rb +18 -0
  43. data/test/test_helper.rb +63 -18
  44. data/test/view_query_test.rb +27 -0
  45. metadata +66 -138
  46. data/Rakefile +0 -11
  47. data/lib/dolly/bulk_error.rb +0 -16
  48. data/lib/dolly/db_config.rb +0 -20
  49. data/lib/dolly/interpreter.rb +0 -5
  50. data/lib/dolly/logger.rb +0 -9
  51. data/lib/dolly/name_space.rb +0 -28
  52. data/lib/dolly/timestamps.rb +0 -21
  53. data/lib/exceptions/dolly.rb +0 -38
  54. data/test/collection_test.rb +0 -59
  55. data/test/configuration_test.rb +0 -9
  56. data/test/dummy/README.rdoc +0 -28
  57. data/test/dummy/Rakefile +0 -6
  58. data/test/dummy/app/assets/javascripts/application.js +0 -13
  59. data/test/dummy/app/assets/stylesheets/application.css +0 -13
  60. data/test/dummy/app/controllers/application_controller.rb +0 -5
  61. data/test/dummy/app/helpers/application_helper.rb +0 -2
  62. data/test/dummy/app/views/layouts/application.html.erb +0 -14
  63. data/test/dummy/bin/bundle +0 -3
  64. data/test/dummy/bin/rails +0 -4
  65. data/test/dummy/bin/rake +0 -4
  66. data/test/dummy/config.ru +0 -4
  67. data/test/dummy/config/application.rb +0 -27
  68. data/test/dummy/config/boot.rb +0 -5
  69. data/test/dummy/config/couchdb.yml +0 -13
  70. data/test/dummy/config/environment.rb +0 -5
  71. data/test/dummy/config/environments/development.rb +0 -29
  72. data/test/dummy/config/environments/production.rb +0 -80
  73. data/test/dummy/config/environments/test.rb +0 -36
  74. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
  75. data/test/dummy/config/initializers/inflections.rb +0 -16
  76. data/test/dummy/config/initializers/mime_types.rb +0 -5
  77. data/test/dummy/config/initializers/secret_token.rb +0 -12
  78. data/test/dummy/config/initializers/session_store.rb +0 -3
  79. data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
  80. data/test/dummy/config/locales/en.yml +0 -23
  81. data/test/dummy/config/routes.rb +0 -56
  82. data/test/dummy/lib/couch_rest_adapter/railtie.rb +0 -10
  83. data/test/dummy/public/404.html +0 -58
  84. data/test/dummy/public/422.html +0 -58
  85. data/test/dummy/public/500.html +0 -57
  86. data/test/dummy/public/favicon.ico +0 -0
  87. data/test/factories/factories.rb +0 -8
@@ -0,0 +1,53 @@
1
+ module Dolly
2
+ module PropertyManager
3
+ def build_property(attributes)
4
+ assign_identity_properties(attributes)
5
+ assign_rev_properties(attributes)
6
+
7
+ lambda do |property|
8
+ name = property.key.to_sym
9
+ next unless doc[name].nil?
10
+ write_attribute(name, attributes[name])
11
+ end
12
+ end
13
+
14
+ def update_attribute
15
+ lambda do |key, value|
16
+ raise InvalidProperty unless valid_property?(key)
17
+ write_attribute(key, value)
18
+ end
19
+ end
20
+
21
+ def write_attribute(key, value)
22
+ value = set_property_value(key, value)
23
+ instance_variable_set(:"@#{key}", value)
24
+ update_doc(key, value)
25
+ end
26
+
27
+ def valid_property?(name)
28
+ properties.include?(name)
29
+ end
30
+
31
+ def update_doc(key, value)
32
+ doc[key] = value
33
+ end
34
+
35
+ def properties
36
+ self.class.properties
37
+ end
38
+
39
+ def set_property_value(key, value)
40
+ properties[key].cast_value(value)
41
+ end
42
+
43
+ def assign_identity_properties(opts = {})
44
+ id_presence = opts[:id] || opts[:_id] || opts['id'] || opts['_id']
45
+ self.id = id_presence if id_presence
46
+ end
47
+
48
+ def assign_rev_properties(opts = {})
49
+ rev_presence = opts[:rev] || opts [:_rev] || opts['rev'] || opts['_rev']
50
+ self.rev = rev_presence if rev_presence
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,23 @@
1
+ module Dolly
2
+ class PropertySet < Set
3
+ def include? key
4
+ keys.include?(key)
5
+ end
6
+
7
+ def <<(property)
8
+ return if include?(property.key)
9
+ super(property)
10
+ end
11
+
12
+ def [](key)
13
+ return detect {|property| property.key == key } if key.is_a?(Symbol)
14
+ super
15
+ end
16
+
17
+ private
18
+
19
+ def keys
20
+ map(&:key)
21
+ end
22
+ end
23
+ end
data/lib/dolly/query.rb CHANGED
@@ -1,85 +1,73 @@
1
- require "dolly/connection"
2
- require "dolly/collection"
3
- require "dolly/name_space"
4
- require "exceptions/dolly"
1
+ require 'dolly/collection'
2
+ require 'dolly/query_arguments'
3
+ require 'dolly/document_type'
4
+ require 'refinements/string_refinements'
5
5
 
6
6
  module Dolly
7
7
  module Query
8
- module ClassMethods
9
- include Dolly::NameSpace
10
- include Dolly::Connection
11
- attr_accessor :properties
12
-
13
- DESIGN_DOC = "dolly"
14
-
15
- def find *keys
16
- query_hash = { keys: keys.map{|key| namespace key} }
17
-
18
- if keys.count > 1
19
- build_collection( query_hash )
20
- else
21
- self.new.from_json( database.all_docs(query_hash).parsed_response )
22
- end
23
- rescue NoMethodError => err
24
- if err.message == "undefined method `[]' for nil:NilClass"
25
- raise Dolly::ResourceNotFound
26
- else
27
- raise
28
- end
29
- end
30
-
31
- def default_query_args
32
- {startkey: "#{name_paramitized}/", endkey: "#{name_paramitized}/\ufff0"}
33
- end
34
-
35
- def all
36
- build_collection default_query_args
37
- end
38
-
39
- def first limit = 1
40
- res = build_collection default_query_args.merge( limit: 1)
41
- limit == 1 ? res.first : res
42
- end
43
-
44
- def last limit = 1
45
- res = build_collection({startkey: default_query_args[:endkey], endkey: default_query_args[:startkey], limit: limit, descending: true})
46
- limit == 1 ? res.first : res
47
- end
48
-
49
- def build_collection q
50
- res = database.all_docs(q)
51
- Collection.new res.parsed_response, name_for_class
52
- end
53
-
54
- def find_with doc, view_name, opts = {}
55
- res = view "_design/#{doc}/_view/#{view_name}", opts
56
- Collection.new res.parsed_response, name_for_class
57
- end
58
-
59
- #TODO: new implementation for collection returning
60
- # multiple types is failling when the class has a namespace
61
- # as the namespace does not exists on the doc id
62
- # we need to reimplement this through a references class method.
63
- def name_for_class
64
- if name.include? '::'
65
- name.constantize
66
- end
67
- end
68
-
69
- def view doc, options = {}
70
- options.merge! include_docs: true
71
- database.get doc, options
72
- end
73
-
74
- def raw_view doc, view, opts = {}
75
- JSON.parse database.get "_design/#{doc}/_view/#{view}", opts
76
- end
8
+ include QueryArguments
9
+ include DocumentType
77
10
 
11
+ using StringRefinements
12
+
13
+ def find *keys
14
+ query_hash = { keys: namespace_keys(keys) }
15
+
16
+ build_collection(query_hash).first_or_all&.itself ||
17
+ raise(Dolly::ResourceNotFound)
18
+ end
19
+
20
+ def bulk_find(*keys_to_find)
21
+ data = {
22
+ query: { include_docs: true },
23
+ keys: keys_to_find.map { |key| namespace_key(key) }
24
+ }
25
+
26
+ res = connection.post('_all_docs', data)
27
+ Collection.new(rows: res, options: { doc_type: self.class_name })
28
+ end
29
+
30
+ def find_all(*keys)
31
+ query_hash = { keys: namespace_keys(keys) }
32
+ return [] if query_hash[:keys].none?
33
+
34
+ keys_to_find_counter = query_hash[:keys].length
35
+ build_collection(query_hash).first_or_all(true)&.itself
36
+ end
37
+
38
+ def safe_find *keys
39
+ find *keys
40
+ rescue Dolly::ResourceNotFound
41
+ nil
78
42
  end
79
43
 
80
- def self.included(base)
81
- base.extend ClassMethods
44
+ def all
45
+ build_collection(default_query_args)
82
46
  end
83
47
 
48
+ def first limit = 1
49
+ query_hash = default_query_args.merge(limit: limit)
50
+ build_collection(query_hash).first_or_all(limit > 1)
51
+ end
52
+
53
+ def last limit = 1
54
+ query_hash = descending_query_args.merge(limit: limit)
55
+ build_collection(query_hash).first_or_all(limit > 1)
56
+ end
57
+
58
+ def find_with doc, view_name, opts = {}
59
+ opts = opts.each_with_object({}) { |(k, v), h| h[k] = v }
60
+ query_results = raw_view(doc, view_name, opts)
61
+
62
+ Collection.new({ rows: query_results, options: {} }).first_or_all
63
+ end
64
+
65
+ def build_collection(query)
66
+ Collection.new({ rows: connection.get('_all_docs', query.merge(include_docs: true)), options: { doc_type: self.class_name }})
67
+ end
68
+
69
+ def bulk_document
70
+ BulkDocument.new(connection)
71
+ end
84
72
  end
85
73
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dolly
4
+ module QueryArguments
5
+ def last_item_in_range
6
+ "\ufff0"
7
+ end
8
+
9
+ def default_query_args
10
+ {
11
+ startkey: "#{name_paramitized}/",
12
+ endkey: "#{name_paramitized}/#{last_item_in_range}"
13
+ }
14
+ end
15
+
16
+ def descending_query_args
17
+ {
18
+ startkey: default_query_args[:endkey],
19
+ endkey: default_query_args[:startkey],
20
+ descending: true
21
+ }
22
+ end
23
+
24
+ def escape_value(value)
25
+ return value if value.is_a? Numeric
26
+ return escape_values(value) if value.is_a? Array
27
+ return CGI.escape(value) if value.is_a? String
28
+ value
29
+ end
30
+
31
+ def escape_values *values
32
+ values.flatten.map { |value| escape_value(value) }
33
+ end
34
+ end
35
+ end
data/lib/dolly/request.rb CHANGED
@@ -1,117 +1,24 @@
1
- require "httparty"
2
- require "dolly/bulk_document"
3
-
4
1
  module Dolly
5
-
6
- class Request
7
- include HTTParty
8
- DEFAULT_HOST = 'localhost'
9
- DEFAULT_PORT = '5984'
10
-
11
- attr_accessor :database_name, :host, :port, :bulk_document
12
-
13
- def initialize options = {}
14
- @host = options["host"] || DEFAULT_HOST
15
- @port = options["port"] || DEFAULT_PORT
16
-
17
- @database_name = options["name"]
18
- @username = options["username"]
19
- @password = options["password"]
20
- @protocol = options["protocol"]
21
-
22
- @bulk_document = Dolly::BulkDocument.new []
23
- self.class.base_uri "#{protocol}://#{host}:#{port}"
24
- end
25
-
26
- def get resource, data = nil
27
- q = {query: values_to_json(data)} if data
28
- request :get, full_path(resource), q
29
- end
30
-
31
- def stats
32
- request :get, "/#{database_name}"
33
- end
34
-
35
- def put resource, data
36
- request :put, full_path(resource), {body: data}
37
- end
38
-
39
- def post resource, data
40
- request :post, full_path(resource), {body: data}
41
- end
42
-
43
- def delete resource
44
- request :delete, full_path(resource), {}
2
+ module Request
3
+ def set_namespace name
4
+ @namespace = name
45
5
  end
46
6
 
47
- def attach resource, attachment_name, data, headers = {}
48
- data = StringIO.new(data) if data.is_a?(String)
49
- request :put, attachment_path(resource, attachment_name), {body: data, headers: headers}
7
+ def set_app_env env
8
+ @app_env = env
50
9
  end
51
10
 
52
- def protocol
53
- @protocol || 'http'
11
+ def connection
12
+ @connection ||= Connection.new(namespace, app_env)
54
13
  end
55
14
 
56
- def uuids opts = {}
57
- tools("_uuids", opts)["uuids"]
15
+ def namespace
16
+ @namespace ||= :default
58
17
  end
59
18
 
60
- def all_docs data = {}
61
- data = values_to_json data.merge( include_docs: true )
62
- request :get, full_path('_all_docs'), {query: data}
63
- end
64
-
65
- def request method, resource, data = nil
66
- data ||= {}
67
- data.merge!(basic_auth: auth_info) if auth_info.present?
68
- headers = { 'Content-Type' => 'application/json' }
69
- headers.merge! data[:headers] if data[:headers]
70
- response = self.class.send method, resource, data.merge(headers: headers)
71
- log_request(resource, response.code) if Dolly.log_requests?
72
- if response.code == 404
73
- raise Dolly::ResourceNotFound
74
- elsif (400..600).include? response.code
75
- raise Dolly::ServerError.new( response )
76
- else
77
- response
78
- end
79
- end
80
-
81
- private
82
- def tools path, opts = nil
83
- data = {}
84
- q = "?#{CGI.unescape(opts.to_query)}" unless opts.blank?
85
- data.merge!(basic_auth: auth_info) if auth_info.present?
86
- JSON::parse self.class.get("/#{path}#{q}", data)
87
- end
88
-
89
- def auth_info
90
- return nil unless @username.present?
91
- {username: @username, password: @password}
92
- end
93
-
94
- def values_to_json hash
95
- hash.reduce({}){|h, v| h[v.first] = v.last.to_json; h}
96
- end
97
-
98
- def full_path resource
99
- "/#{database_name}/#{resource}"
100
- end
101
-
102
- def attachment_path resource, attachment_name
103
- "#{full_path(resource)}/#{attachment_name}"
104
- end
105
-
106
- def log_request resource, response_code
107
- log_value = ->(resource, response_code) { "Query: #{resource}, Response Code: #{response_code}" }
108
- case response_code
109
- when 200..399
110
- Dolly.logger.info log_value[resource, response_code]
111
- when 400..600
112
- Dolly.logger.warn log_value[resource, response_code]
113
- end
19
+ def app_env
20
+ return Rails.env if defined? Rails.env
21
+ @app_env ||= :development
114
22
  end
115
23
  end
116
-
117
24
  end
@@ -0,0 +1,26 @@
1
+ require 'forwardable'
2
+
3
+ module Dolly
4
+ class HeaderRequest
5
+ extend Forwardable
6
+
7
+ CONTENT_TYPE_KEY = 'Content-Type'
8
+ JSON_CONTENT = 'application/json'
9
+
10
+ def_delegators :@collection, :[], :[]=, :keys, :each, :present?, :merge!
11
+
12
+ def initialize hash = nil
13
+ @collection = hash || default_value
14
+ end
15
+
16
+ def json?
17
+ @collection[CONTENT_TYPE_KEY] == JSON_CONTENT
18
+ end
19
+
20
+ private
21
+
22
+ def default_value
23
+ { CONTENT_TYPE_KEY => JSON_CONTENT }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,24 @@
1
+ module Dolly
2
+ module Timestamp
3
+ def write_timestamps(is_persisted)
4
+ return unless timestamped?
5
+ write_attribute(:created_at, Time.now) unless is_persisted
6
+ write_attribute(:updated_at, Time.now)
7
+ end
8
+
9
+ def timestamped?
10
+ respond_to?(:created_at) && respond_to?(:updated_at)
11
+ end
12
+
13
+ def self.included(base)
14
+ base.extend(ClassMethods)
15
+ end
16
+
17
+ module ClassMethods
18
+ def timestamps!
19
+ property :created_at, :updated_at, class_name: Time
20
+ end
21
+ end
22
+ end
23
+ end
24
+