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