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
@@ -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
@@ -1,85 +1,55 @@
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
8
+ include QueryArguments
9
+ include DocumentType
12
10
 
13
- DESIGN_DOC = "dolly"
11
+ using StringRefinements
14
12
 
15
- def find *keys
16
- query_hash = { keys: keys.map{|key| namespace key} }
13
+ def find *keys
14
+ query_hash = { keys: namespace_keys(keys).map { |k| k.cgi_escape } }
17
15
 
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
16
+ build_collection(query_hash).first_or_all&.itself ||
17
+ raise(Dolly::ResourceNotFound)
18
+ end
48
19
 
49
- def build_collection q
50
- res = database.all_docs(q)
51
- Collection.new res.parsed_response, name_for_class
52
- end
20
+ def safe_find *keys
21
+ find *keys
22
+ rescue Dolly::ResourceNotFound
23
+ nil
24
+ end
53
25
 
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
26
+ def all
27
+ build_collection(default_query_args)
28
+ end
58
29
 
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
30
+ def first limit = 1
31
+ query_hash = default_query_args.merge(limit: limit)
32
+ build_collection(query_hash).first_or_all(limit > 1)
33
+ end
68
34
 
69
- def view doc, options = {}
70
- options.merge! include_docs: true
71
- database.get doc, options
72
- end
35
+ def last limit = 1
36
+ query_hash = descending_query_args.merge(limit: limit)
37
+ build_collection(query_hash).first_or_all(limit > 1)
38
+ end
73
39
 
74
- def raw_view doc, view, opts = {}
75
- JSON.parse database.get "_design/#{doc}/_view/#{view}", opts
76
- end
40
+ def find_with doc, view_name, opts = {}
41
+ opts = opts.each_with_object({}) { |(k, v), h| h[k] = escape_value(v) }
42
+ query_results = raw_view(doc, view_name, opts)
77
43
 
44
+ Collection.new({ rows: query_results, options: {} }).first_or_all
78
45
  end
79
46
 
80
- def self.included(base)
81
- base.extend ClassMethods
47
+ def build_collection(query)
48
+ Collection.new({ rows: connection.get('_all_docs', query.merge(include_docs: true)), options: { doc_type: self.class_name }})
82
49
  end
83
50
 
51
+ def bulk_document
52
+ BulkDocument.new(connection)
53
+ end
84
54
  end
85
55
  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,106 +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
2
+ module Request
3
+ def set_namespace name
4
+ @namespace = name
29
5
  end
30
6
 
31
- def stats
32
- request :get, "/#{database_name}"
7
+ def set_app_env env
8
+ @app_env = env
33
9
  end
34
10
 
35
- def put resource, data
36
- request :put, full_path(resource), {body: data}
11
+ def connection
12
+ @connection ||= Connection.new(namespace, app_env)
37
13
  end
38
14
 
39
- def post resource, data
40
- request :post, full_path(resource), {body: data}
15
+ def namespace
16
+ @namespace ||= :default
41
17
  end
42
18
 
43
- def delete resource
44
- request :delete, full_path(resource), {}
45
- end
46
-
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}
50
- end
51
-
52
- def protocol
53
- @protocol || 'http'
54
- end
55
-
56
- def uuids opts = {}
57
- tools("_uuids", opts)["uuids"]
58
- end
59
-
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
- if response.code == 404
72
- raise Dolly::ResourceNotFound
73
- elsif (400..600).include? response.code
74
- raise Dolly::ServerError.new( response )
75
- else
76
- response
77
- end
78
- end
79
-
80
- private
81
- def tools path, opts = nil
82
- data = {}
83
- q = "?#{CGI.unescape(opts.to_query)}" unless opts.blank?
84
- data.merge!(basic_auth: auth_info) if auth_info.present?
85
- JSON::parse self.class.get("/#{path}#{q}", data)
86
- end
87
-
88
- def auth_info
89
- return nil unless @username.present?
90
- {username: @username, password: @password}
91
- end
92
-
93
- def values_to_json hash
94
- hash.reduce({}){|h, v| h[v.first] = v.last.to_json; h}
95
- end
96
-
97
- def full_path resource
98
- "/#{database_name}/#{resource}"
99
- end
100
-
101
- def attachment_path resource, attachment_name
102
- "#{full_path(resource)}/#{attachment_name}"
19
+ def app_env
20
+ return Rails.env if defined? Rails.env
21
+ @app_env ||= :development
103
22
  end
104
23
  end
105
-
106
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
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
+
@@ -1,3 +1,3 @@
1
1
  module Dolly
2
- VERSION = "1.1.4"
2
+ VERSION = "3.0.1"
3
3
  end
@@ -0,0 +1,14 @@
1
+ require 'dolly/class_methods_delegation'
2
+
3
+ module Dolly
4
+ module ViewQuery
5
+ def raw_view(doc, view_name, opts = {})
6
+ design = "_design/#{doc}/_view/#{view_name}"
7
+ connection.view(design, opts)
8
+ end
9
+
10
+ def view_value(doc, view_name, opts = {})
11
+ raw_view(doc, view_name, opts)[:rows].flat_map { |result| result[:value] }
12
+ end
13
+ end
14
+ end
@@ -1,4 +1,4 @@
1
- require 'rails'
1
+ require 'rails/railtie'
2
2
 
3
3
  module Dolly
4
4
  class Railtie < Rails::Railtie
@@ -10,3 +10,4 @@ module Dolly
10
10
  end
11
11
  end
12
12
 
13
+
@@ -0,0 +1,27 @@
1
+ module HashRefinements
2
+ refine Hash do
3
+ # File activesupport/lib/active_support/core_ext/hash/keys.rb, line 82
4
+ def deep_transform_keys(&block)
5
+ _deep_transform_keys_in_object(self, &block)
6
+ end
7
+
8
+ def slice(*keys)
9
+ keys.each_with_object(Hash.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
10
+ end
11
+
12
+ private
13
+
14
+ def _deep_transform_keys_in_object(object, &block)
15
+ case object
16
+ when Hash
17
+ object.each_with_object({}) do |(key, value), result|
18
+ result[yield(key)] = _deep_transform_keys_in_object(value, &block)
19
+ end
20
+ when Array
21
+ object.map { |e| _deep_transform_keys_in_object(e, &block) }
22
+ else
23
+ object
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ module StringRefinements
2
+ UNESCAPABLE_PATTERNS = [
3
+ %r{_design/.+/_view/.+}
4
+ ]
5
+
6
+ refine String do
7
+ #FROM ActiveModel::Name
8
+ def underscore
9
+ to_s.gsub(/::/, '/').
10
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
11
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
12
+ tr("-", "_").
13
+ downcase
14
+ end
15
+
16
+ def cgi_escape
17
+ return if nil?
18
+ return self unless escapable?
19
+ CGI.escape self
20
+ end
21
+
22
+ def escapable?
23
+ UNESCAPABLE_PATTERNS.none? do |pattern|
24
+ self =~ pattern
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  namespace :db do
2
4
  desc "Will create if missing database and add default views"
3
5
  task setup: :environment do
4
- Dolly::Document.database.put "", nil
6
+ Dolly::Document.connection.put '', {}
5
7
  puts "Database created"
6
8
  end
7
9
 
@@ -38,9 +40,9 @@ namespace :db do
38
40
  view_doc.merge!( '_id' => design_doc_name, 'language' => 'coffeescript')
39
41
 
40
42
  begin
41
- hash_doc = JSON::parse Dolly::Document.database.get(view_doc["_id"]).parsed_response
43
+ hash_doc = Dolly::Document.connection.request(:get, view_doc["_id"])
42
44
 
43
- rev = hash_doc.delete('_rev')
45
+ rev = hash_doc.delete(:_rev)
44
46
 
45
47
  if hash_doc == view_doc
46
48
  puts 'everything up to date'
@@ -55,8 +57,22 @@ namespace :db do
55
57
  will_save = true
56
58
  end
57
59
 
58
- Dolly::Document.database.put design_doc_name, view_doc.to_json if will_save
60
+ Dolly::Document.connection.request :put, design_doc_name, view_doc if will_save
59
61
  end
60
62
  end
61
63
 
64
+ namespace :index do
65
+ desc 'Creates indexes for mango querys located in db/indexes/*.json'
66
+ task create: :environment do
67
+ indexes_dir = Rails.root.join('db', 'indexes')
68
+ files = Dir.glob File.join(indexes_dir, '**', '*.json')
69
+
70
+ files.each do |file|
71
+ index_data = JSON.parse(File.read(file))
72
+ puts "Creating index: #{index_data["name"]}"
73
+ puts Dolly::MangoIndex.create(index_data['name'], index_data['fields'])
74
+ end
75
+ end
76
+ end
62
77
  end
78
+