dolly 1.1.4 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+