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
@@ -1,85 +1,96 @@
1
+ require 'refinements/string_refinements'
2
+
1
3
  module Dolly
2
4
  class Property
3
- attr_writer :value
4
- attr_accessor :name
5
- attr_reader :class_name, :default
5
+ attr_reader :key, :class_name, :default
6
+ CANT_CLONE = [NilClass, TrueClass, FalseClass, Integer]
6
7
 
7
- CANT_CLONE = [NilClass, TrueClass, FalseClass, Fixnum].freeze
8
+ using StringRefinements
8
9
 
9
- def initialize opts = {}
10
- @class_name = opts.delete(:class_name) if opts.present?
11
- @name = opts.delete(:name).to_s
12
- @default = opts.delete(:default)
13
- @default = @default.clone if @default && CANT_CLONE.none? { |klass| @default.is_a? klass }
14
- @value = @default if @default
15
- warn 'There are some unprocessed options!' if opts.present?
10
+ def initialize(key, class_name, default = nil)
11
+ @key = key
12
+ @default = default
13
+ @class_name = class_name
16
14
  end
17
15
 
18
- def value
19
- #TODO: tets if this actually sets `doc[ "name" ]`
20
- return @default if @value.nil?
21
- return @value unless self_klass
22
-
23
- klass_sym = :"#{self_klass.name.underscore}_#{__method__}"
16
+ def cast_value(value)
17
+ return set_default if value.nil?
18
+ return value unless class_name
19
+ return self_klass.new(value) unless respond_to?(klass_sym)
20
+ send(klass_sym, value)
21
+ end
24
22
 
25
- return self_klass.new @value unless self.respond_to?(klass_sym)
23
+ def boolean?
24
+ [TrueClass, FalseClass].include?(class_name)
25
+ end
26
26
 
27
- self.send klass_sym
27
+ def string_value(value)
28
+ value.to_s
28
29
  end
29
30
 
30
- def array_value
31
- @value.to_a
31
+ def hash_value(value)
32
+ value.to_h
32
33
  end
33
34
 
34
- def hash_value
35
- @value.to_h
35
+ def integer_value(value)
36
+ value.to_i
36
37
  end
37
38
 
38
- def string_value
39
- @value.to_s
39
+ def float_value(value)
40
+ value.to_f
40
41
  end
41
42
 
42
- def integer_value
43
- @value.to_i
43
+ def date_value(value)
44
+ return value.to_date if value.respond_to?(:to_date)
45
+ Date.parse(value)
44
46
  end
45
47
 
46
- def float_value
47
- @value.to_f
48
+ def time_value(value)
49
+ return value.to_time if value.respond_to?(:to_time)
50
+ DateTime.parse(value).to_time
48
51
  end
49
52
 
50
- def date_value
51
- @value.to_date
53
+ def date_time_value(value)
54
+ return value.to_datetime if value.respond_to?(:to_datetime)
55
+ DateTime.parse(value)
52
56
  end
53
57
 
54
- def time_value
55
- @value.to_time
58
+ def true_class_value(value)
59
+ truthy_value?(value)
56
60
  end
57
61
 
58
- def date_time_value
59
- @value.to_datetime
62
+ def false_class_value(value)
63
+ truthy_value?(value)
60
64
  end
61
65
 
62
- def true_class_value
63
- truthy_value?
66
+ private
67
+
68
+ def truthy_value?(value)
69
+ value =~ /true/ || value === true
64
70
  end
65
71
 
66
- def false_class_value
67
- truthy_value?
72
+ def klass_sym
73
+ :"#{self_klass.name.underscore}_value"
68
74
  end
69
75
 
70
- def boolean?
71
- self_klass == TrueClass || self_klass == FalseClass
76
+ def self_klass
77
+ return unless class_name
78
+ return class_name if class_name.is_a?(Class)
79
+ Object.const_get class_name
72
80
  end
73
81
 
74
- private
75
- def truthy_value?
76
- @value =~ /true/ || @value === true
82
+ def set_default
83
+ return unless default_present?
84
+ return default unless cant_clone_default?
85
+ default.clone
77
86
  end
78
87
 
79
- def self_klass
80
- return unless @class_name
81
- @class_name.is_a?(Class)? @class_name : @class_name.constantize
88
+ def cant_clone_default?
89
+ CANT_CLONE.none? { |klass| default.is_a? klass }
82
90
  end
83
91
 
92
+ def default_present?
93
+ !default.nil?
94
+ end
84
95
  end
85
96
  end
@@ -0,0 +1,47 @@
1
+ module Dolly
2
+ module PropertyManager
3
+ def build_property(attributes)
4
+ assign_identity_properties(attributes)
5
+
6
+ lambda do |property|
7
+ name = property.key.to_sym
8
+ next unless doc[name].nil?
9
+ write_attribute(name, attributes[name])
10
+ end
11
+ end
12
+
13
+ def update_attribute
14
+ lambda do |key, value|
15
+ raise InvalidProperty unless valid_property?(key)
16
+ write_attribute(key, value)
17
+ end
18
+ end
19
+
20
+ def write_attribute key, value
21
+ value = set_property_value(key, value)
22
+ instance_variable_set(:"@#{key}", value)
23
+ update_doc(key, value) unless value.nil?
24
+ end
25
+
26
+ def valid_property?(name)
27
+ properties.include? name
28
+ end
29
+
30
+ def update_doc(key, value)
31
+ doc[key] = value
32
+ end
33
+
34
+ def properties
35
+ self.class.properties
36
+ end
37
+
38
+ def set_property_value(key, value)
39
+ properties[key].cast_value(value)
40
+ end
41
+
42
+ def assign_identity_properties(opts = {})
43
+ id_presence = opts[:id] || opts[:_id] || opts['id'] || opts['_id']
44
+ self.id = id_presence if id_presence
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,18 @@
1
+ module Dolly
2
+ class PropertySet < Set
3
+ def include? key
4
+ keys.include?(key)
5
+ end
6
+
7
+ def [](key)
8
+ return detect {|property| property.key == key } if key.is_a?(Symbol)
9
+ super
10
+ end
11
+
12
+ private
13
+
14
+ def keys
15
+ map(&:key)
16
+ end
17
+ end
18
+ end
@@ -1,85 +1,57 @@
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'
5
4
 
6
5
  module Dolly
7
6
  module Query
8
- module ClassMethods
9
- include Dolly::NameSpace
10
- include Dolly::Connection
11
- attr_accessor :properties
7
+ include QueryArguments
8
+ include DocumentType
12
9
 
13
- DESIGN_DOC = "dolly"
10
+ def find *keys
11
+ query_hash = { keys: namespace_keys(keys) }
14
12
 
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
13
+ build_collection(query_hash).first_or_all&.itself ||
14
+ raise(Dolly::ResourceNotFound)
15
+ end
43
16
 
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
17
+ def safe_find *keys
18
+ find *keys
19
+ rescue Dolly::ResourceNotFound
20
+ nil
21
+ end
48
22
 
49
- def build_collection q
50
- res = database.all_docs(q)
51
- Collection.new res.parsed_response, name_for_class
52
- end
23
+ def all
24
+ build_collection(default_query_args)
25
+ end
53
26
 
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
27
+ def first limit = 1
28
+ query_hash = default_query_args.merge(limit: limit)
29
+ build_collection(query_hash).first_or_all(limit > 1)
30
+ end
58
31
 
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
32
+ def last limit = 1
33
+ query_hash = descending_query_args.merge(limit: limit)
34
+ build_collection(query_hash).first_or_all(limit > 1)
35
+ end
68
36
 
69
- def view doc, options = {}
70
- options.merge! include_docs: true
71
- database.get doc, options
72
- end
37
+ def find_with doc, view_name, opts = {}
38
+ opts = opts.each_with_object({}) { |(k, v), h| h[k] = escape_value(v) }
39
+ query_results = raw_view(doc, view_name, opts)
73
40
 
74
- def raw_view doc, view, opts = {}
75
- JSON.parse database.get "_design/#{doc}/_view/#{view}", opts
76
- end
41
+ Collection.new(query_results).first_or_all
42
+ end
77
43
 
44
+ def raw_view doc, view_name, opts = {}
45
+ design = "_design/#{doc}/_view/#{view_name}"
46
+ connection.view(design, opts)
78
47
  end
79
48
 
80
- def self.included(base)
81
- base.extend ClassMethods
49
+ def build_collection(query)
50
+ Collection.new(connection.get('_all_docs', query.merge(include_docs: true)))
82
51
  end
83
52
 
53
+ def bulk_document
54
+ BulkDocument.new(connection)
55
+ end
84
56
  end
85
57
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dolly
4
+ module QueryArguments
5
+ def last_item_in_range
6
+ URI.escape("\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
@@ -1,119 +1,24 @@
1
- require "httparty"
2
- require "dolly/bulk_document"
3
-
4
1
  module Dolly
5
-
6
- class Request
7
- include HTTParty
8
- REQUIRED_KEYS = %w/host port name/.freeze
9
-
10
- attr_accessor :database_name, :host, :port, :bulk_document
11
-
12
- def initialize options = {}
13
- REQUIRED_KEYS.each do |key|
14
- raise Dolly::MissingRequestConfigSettings.new(key) unless options[key]
15
- end
16
-
17
- @host = options["host"]
18
- @port = options["port"]
19
- @database_name = options["name"]
20
- @username = options["username"]
21
- @password = options["password"]
22
- @protocol = options["protocol"]
23
-
24
- @bulk_document = Dolly::BulkDocument.new []
25
- self.class.base_uri "#{protocol}://#{host}:#{port}"
26
- end
27
-
28
- def get resource, data = nil
29
- q = {query: values_to_json(data)} if data
30
- request :get, full_path(resource), q
31
- end
32
-
33
- def stats
34
- request :get, "/#{database_name}"
35
- end
36
-
37
- def put resource, data
38
- request :put, full_path(resource), {body: data}
39
- end
40
-
41
- def post resource, data
42
- request :post, full_path(resource), {body: data}
43
- end
44
-
45
- def delete resource
46
- request :delete, full_path(resource), {}
2
+ module Request
3
+ def set_namespace name
4
+ @namspace = name
47
5
  end
48
6
 
49
- def attach resource, attachment_name, data, headers = {}
50
- data = StringIO.new(data) if data.is_a?(String)
51
- request :put, attachment_path(resource, attachment_name), {body: data, headers: headers}
7
+ def set_app_env env
8
+ @app_env = env
52
9
  end
53
10
 
54
- def protocol
55
- @protocol ||= 'http'
11
+ def connection
12
+ @connection ||= Connection.new(namespace, app_env)
56
13
  end
57
14
 
58
- def uuids opts = {}
59
- tools("_uuids", opts)["uuids"]
15
+ def namespace
16
+ @namespace ||= :default
60
17
  end
61
18
 
62
- def all_docs data = {}
63
- data = values_to_json data.merge( include_docs: true )
64
- request :get, full_path('_all_docs'), {query: data}
65
- end
66
-
67
- def request method, resource, data = nil
68
- data ||= {}
69
- data.merge!(basic_auth: auth_info) if auth_info.present?
70
- headers = { 'Content-Type' => 'application/json' }
71
- headers.merge! data[:headers] if data[:headers]
72
- response = self.class.send method, resource, data.merge(headers: headers)
73
- log_request(resource, response.code) if Dolly.log_requests?
74
- if response.code == 404
75
- raise Dolly::ResourceNotFound
76
- elsif (400..600).include? response.code
77
- raise Dolly::ServerError.new( response )
78
- else
79
- response
80
- end
81
- end
82
-
83
- private
84
- def tools path, opts = nil
85
- data = {}
86
- q = "?#{CGI.unescape(opts.to_query)}" unless opts.blank?
87
- data.merge!(basic_auth: auth_info) if auth_info.present?
88
- JSON::parse self.class.get("/#{path}#{q}", data)
89
- end
90
-
91
- def auth_info
92
- return nil unless @username.present?
93
- {username: @username, password: @password}
94
- end
95
-
96
- def values_to_json hash
97
- hash.reduce({}){|h, v| h[v.first] = v.last.to_json; h}
98
- end
99
-
100
- def full_path resource
101
- "/#{database_name}/#{resource}"
102
- end
103
-
104
- def attachment_path resource, attachment_name
105
- "#{full_path(resource)}/#{attachment_name}"
106
- end
107
-
108
- def log_request resource, response_code
109
- log_value = ->(resource, response_code) { "Query: #{resource}, Response Code: #{response_code}" }
110
- case response_code
111
- when 200..399
112
- Dolly.logger.info log_value[resource, response_code]
113
- when 400..600
114
- Dolly.logger.warn log_value[resource, response_code]
115
- end
19
+ def app_env
20
+ return Rails.env if defined? Rails.env
21
+ @app_env ||= :development
116
22
  end
117
23
  end
118
-
119
24
  end