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.
- checksums.yaml +5 -5
- data/README.md +78 -0
- data/lib/dolly.rb +4 -2
- data/lib/dolly/attachment.rb +29 -0
- data/lib/dolly/bulk_document.rb +27 -26
- data/lib/dolly/class_methods_delegation.rb +15 -0
- data/lib/dolly/collection.rb +32 -65
- data/lib/dolly/configuration.rb +43 -0
- data/lib/dolly/connection.rb +101 -22
- data/lib/dolly/depracated_database.rb +24 -0
- data/lib/dolly/document.rb +48 -198
- data/lib/dolly/document_creation.rb +20 -0
- data/lib/dolly/document_state.rb +66 -0
- data/lib/dolly/document_type.rb +47 -0
- data/lib/dolly/exceptions.rb +32 -0
- data/lib/dolly/identity_properties.rb +29 -0
- data/lib/dolly/mango.rb +124 -0
- data/lib/dolly/mango_index.rb +65 -0
- data/lib/dolly/properties.rb +36 -0
- data/lib/dolly/property.rb +58 -47
- data/lib/dolly/property_manager.rb +47 -0
- data/lib/dolly/property_set.rb +23 -0
- data/lib/dolly/query.rb +37 -67
- data/lib/dolly/query_arguments.rb +35 -0
- data/lib/dolly/request.rb +12 -94
- data/lib/dolly/request_header.rb +26 -0
- data/lib/dolly/timestamp.rb +24 -0
- data/lib/dolly/version.rb +1 -1
- data/lib/dolly/view_query.rb +14 -0
- data/lib/{dolly → railties}/railtie.rb +2 -1
- data/lib/refinements/hash_refinements.rb +27 -0
- data/lib/refinements/string_refinements.rb +28 -0
- data/lib/tasks/db.rake +20 -4
- data/test/bulk_document_test.rb +8 -5
- data/test/document_test.rb +132 -95
- data/test/document_type_test.rb +28 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/log/test.log +111132 -0
- data/test/inheritance_test.rb +23 -0
- data/test/mango_index_test.rb +64 -0
- data/test/mango_test.rb +273 -0
- data/test/test_helper.rb +63 -18
- data/test/view_query_test.rb +27 -0
- metadata +57 -141
- data/Rakefile +0 -11
- data/lib/dolly/bulk_error.rb +0 -16
- data/lib/dolly/db_config.rb +0 -20
- data/lib/dolly/interpreter.rb +0 -5
- data/lib/dolly/name_space.rb +0 -28
- data/lib/dolly/timestamps.rb +0 -21
- data/lib/exceptions/dolly.rb +0 -38
- data/test/collection_test.rb +0 -59
- data/test/dummy/README.rdoc +0 -28
- data/test/dummy/Rakefile +0 -6
- data/test/dummy/app/assets/javascripts/application.js +0 -13
- data/test/dummy/app/assets/stylesheets/application.css +0 -13
- data/test/dummy/app/controllers/application_controller.rb +0 -5
- data/test/dummy/app/helpers/application_helper.rb +0 -2
- data/test/dummy/app/views/layouts/application.html.erb +0 -14
- data/test/dummy/bin/bundle +0 -3
- data/test/dummy/bin/rails +0 -4
- data/test/dummy/bin/rake +0 -4
- data/test/dummy/config.ru +0 -4
- data/test/dummy/config/application.rb +0 -27
- data/test/dummy/config/boot.rb +0 -5
- data/test/dummy/config/couchdb.yml +0 -13
- data/test/dummy/config/environment.rb +0 -5
- data/test/dummy/config/environments/development.rb +0 -29
- data/test/dummy/config/environments/production.rb +0 -80
- data/test/dummy/config/environments/test.rb +0 -36
- data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/test/dummy/config/initializers/inflections.rb +0 -16
- data/test/dummy/config/initializers/mime_types.rb +0 -5
- data/test/dummy/config/initializers/secret_token.rb +0 -12
- data/test/dummy/config/initializers/session_store.rb +0 -3
- data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
- data/test/dummy/config/locales/en.yml +0 -23
- data/test/dummy/config/routes.rb +0 -56
- data/test/dummy/lib/couch_rest_adapter/railtie.rb +0 -10
- data/test/dummy/public/404.html +0 -58
- data/test/dummy/public/422.html +0 -58
- data/test/dummy/public/500.html +0 -57
- data/test/dummy/public/favicon.ico +0 -0
- 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
|
data/lib/dolly/query.rb
CHANGED
@@ -1,85 +1,55 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
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
|
-
|
9
|
-
|
10
|
-
include Dolly::Connection
|
11
|
-
attr_accessor :properties
|
8
|
+
include QueryArguments
|
9
|
+
include DocumentType
|
12
10
|
|
13
|
-
|
11
|
+
using StringRefinements
|
14
12
|
|
15
|
-
|
16
|
-
|
13
|
+
def find *keys
|
14
|
+
query_hash = { keys: namespace_keys(keys).map { |k| k.cgi_escape } }
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
20
|
+
def safe_find *keys
|
21
|
+
find *keys
|
22
|
+
rescue Dolly::ResourceNotFound
|
23
|
+
nil
|
24
|
+
end
|
53
25
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end
|
26
|
+
def all
|
27
|
+
build_collection(default_query_args)
|
28
|
+
end
|
58
29
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
75
|
-
|
76
|
-
|
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
|
81
|
-
|
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
|
data/lib/dolly/request.rb
CHANGED
@@ -1,106 +1,24 @@
|
|
1
|
-
require "httparty"
|
2
|
-
require "dolly/bulk_document"
|
3
|
-
|
4
1
|
module Dolly
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
32
|
-
|
7
|
+
def set_app_env env
|
8
|
+
@app_env = env
|
33
9
|
end
|
34
10
|
|
35
|
-
def
|
36
|
-
|
11
|
+
def connection
|
12
|
+
@connection ||= Connection.new(namespace, app_env)
|
37
13
|
end
|
38
14
|
|
39
|
-
def
|
40
|
-
|
15
|
+
def namespace
|
16
|
+
@namespace ||= :default
|
41
17
|
end
|
42
18
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
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
|
+
|
data/lib/dolly/version.rb
CHANGED
@@ -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
|
@@ -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
|
data/lib/tasks/db.rake
CHANGED
@@ -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.
|
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 =
|
43
|
+
hash_doc = Dolly::Document.connection.request(:get, view_doc["_id"])
|
42
44
|
|
43
|
-
rev = hash_doc.delete(
|
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.
|
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
|
+
|