dolly 3.0.0 → 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 +43 -0
- data/lib/dolly.rb +1 -0
- data/lib/dolly/bulk_document.rb +1 -1
- data/lib/dolly/collection.rb +17 -7
- data/lib/dolly/connection.rb +20 -10
- data/lib/dolly/document.rb +21 -1
- data/lib/dolly/document_state.rb +1 -0
- data/lib/dolly/document_type.rb +21 -2
- data/lib/dolly/exceptions.rb +11 -0
- data/lib/dolly/mango.rb +124 -0
- data/lib/dolly/mango_index.rb +65 -0
- data/lib/dolly/properties.rb +6 -1
- data/lib/dolly/property_set.rb +5 -0
- data/lib/dolly/query.rb +6 -8
- data/lib/dolly/request.rb +1 -1
- data/lib/dolly/version.rb +1 -1
- data/lib/dolly/view_query.rb +14 -0
- data/lib/refinements/hash_refinements.rb +27 -0
- data/lib/tasks/db.rake +16 -1
- data/test/document_test.rb +0 -49
- data/test/document_type_test.rb +28 -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 +51 -0
- data/test/view_query_test.rb +27 -0
- metadata +17 -4
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 | 
            -
             | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 2 | 
            +
            SHA256:
         | 
| 3 | 
            +
              metadata.gz: 52fcc7a3f0888239aaff1a6398a81112d031722db14d3070d83d574179414197
         | 
| 4 | 
            +
              data.tar.gz: 20d5efb134448842c4c547d85f0f395993fdd8817898e7b174e3e5f5714a7022
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: f684874eaa87448a0ae67fddf5df98f3e86c5c6a53f8fb7b75e1331d16d63f07e6ef8eb2db17e461609433ccaf770a1c3a3c27b1551b5cf7c823f2d010005075
         | 
| 7 | 
            +
              data.tar.gz: f44fa8ea168a1d6ca31042c4eed5b8cf0fdb1fd11dfcc5fb76e177598cce96512e6cca8a842e6c646fa4f65f8e4e0ea9759379ad73954c42fb1694c9803d42c3
         | 
    
        data/README.md
    CHANGED
    
    | @@ -33,3 +33,46 @@ To install this gem onto your local machine, run `bundle exec rake install`. To | |
| 33 33 | 
             
            ## Contributing
         | 
| 34 34 |  | 
| 35 35 | 
             
            Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/dolly3.
         | 
| 36 | 
            +
             | 
| 37 | 
            +
             | 
| 38 | 
            +
            ## Migrating from couch 1.x to 2.x
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            [Official docs](https://docs.couchdb.org/en/2.3.1/install/index.html)
         | 
| 41 | 
            +
             | 
| 42 | 
            +
             | 
| 43 | 
            +
            You will need to uninstall the couchdb service with brew
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            `brew services stop couchdb`
         | 
| 46 | 
            +
            `brew services uninstall couchdb`
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            Download the application from the following source
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            http://couchdb.apache.org/#download
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            launch fauxton and check your installation
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            Copy [this file](https://github.com/apache/couchdb/blob/master/rel/overlay/bin/couchup) into your filesystem
         | 
| 55 | 
            +
             | 
| 56 | 
            +
             | 
| 57 | 
            +
            make it executable
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            `chmod +x couchup.py`
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            and run it
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            `./couchup.py -h`
         | 
| 64 | 
            +
             | 
| 65 | 
            +
            You might need to install python 3 and pip3 and the following libs
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            `pip3 install requests progressbar2`
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            move your .couch files into the specified `database_dir` in your [fauxton config](http://127.0.0.1:5984/_utils/#_config/couchdb@localhost)
         | 
| 70 | 
            +
             | 
| 71 | 
            +
             | 
| 72 | 
            +
            ```
         | 
| 73 | 
            +
            $ ./couchup list           # Shows your unmigrated 1.x databases
         | 
| 74 | 
            +
            $ ./couchup replicate -a   # Replicates your 1.x DBs to 2.x
         | 
| 75 | 
            +
            $ ./couchup rebuild -a     # Optional; starts rebuilding your views
         | 
| 76 | 
            +
            $ ./couchup delete -a      # Deletes your 1.x DBs (careful!)
         | 
| 77 | 
            +
            $ ./couchup list           # Should show no remaining databases!
         | 
| 78 | 
            +
            ```
         | 
    
        data/lib/dolly.rb
    CHANGED
    
    
    
        data/lib/dolly/bulk_document.rb
    CHANGED
    
    | @@ -80,7 +80,7 @@ module Dolly | |
| 80 80 | 
             
                end
         | 
| 81 81 |  | 
| 82 82 | 
             
                def response_error(item)
         | 
| 83 | 
            -
                  BulkError.new(error: 'Document saved but not local rev updated.', reason: "Document with id #{ | 
| 83 | 
            +
                  BulkError.new(error: 'Document saved but not local rev updated.', reason: "Document with id #{item} on bulk doc was not found in payload.", obj: nil)
         | 
| 84 84 | 
             
                end
         | 
| 85 85 | 
             
              end
         | 
| 86 86 | 
             
            end
         | 
    
        data/lib/dolly/collection.rb
    CHANGED
    
    | @@ -1,12 +1,12 @@ | |
| 1 1 | 
             
            module Dolly
         | 
| 2 2 | 
             
              class Collection < DelegateClass(Array)
         | 
| 3 | 
            -
                attr_reader : | 
| 3 | 
            +
                attr_reader :options
         | 
| 4 4 |  | 
| 5 | 
            -
                def initialize(rows: [],  | 
| 6 | 
            -
                  @ | 
| 5 | 
            +
                def initialize(rows: [], options: {})
         | 
| 6 | 
            +
                  @options = options
         | 
| 7 7 | 
             
                  #TODO: We should raise an exception if one of the
         | 
| 8 8 | 
             
                  #      requested documents is missing
         | 
| 9 | 
            -
                  super rows.map(&collect_docs).compact
         | 
| 9 | 
            +
                  super rows[:rows].map(&collect_docs).compact
         | 
| 10 10 | 
             
                end
         | 
| 11 11 |  | 
| 12 12 | 
             
                def first_or_all(forced_first = false)
         | 
| @@ -23,13 +23,23 @@ module Dolly | |
| 23 23 | 
             
                def collect_docs
         | 
| 24 24 | 
             
                  lambda do |row|
         | 
| 25 25 | 
             
                    next unless collectable_row?(row)
         | 
| 26 | 
            -
                    klass = Object.const_get | 
| 26 | 
            +
                    klass = Object.const_get(doc_model(row))
         | 
| 27 27 | 
             
                    klass.from_doc(row[:doc])
         | 
| 28 28 | 
             
                  end
         | 
| 29 29 | 
             
                end
         | 
| 30 30 |  | 
| 31 | 
            -
                def  | 
| 32 | 
            -
                   | 
| 31 | 
            +
                def doc_model(doc)
         | 
| 32 | 
            +
                  options[:doc_type] || constantize_key(doc[:doc_type]) || constantize_key(doc_type_for(doc[:id]))
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def doc_type_for(key)
         | 
| 36 | 
            +
                  return false if key.nil?
         | 
| 37 | 
            +
                  key.match(%r{^([^/]+)/})[1]
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def constantize_key(key)
         | 
| 41 | 
            +
                  return false if key.nil?
         | 
| 42 | 
            +
                  key.split('_').collect(&:capitalize).join
         | 
| 33 43 | 
             
                end
         | 
| 34 44 |  | 
| 35 45 | 
             
                def collectable_row?(row)
         | 
    
        data/lib/dolly/connection.rb
    CHANGED
    
    | @@ -12,6 +12,7 @@ module Dolly | |
| 12 12 | 
             
                attr_reader :db, :app_env
         | 
| 13 13 |  | 
| 14 14 | 
             
                DEFAULT_HEADER = { 'Content-Type' => 'application/json' }
         | 
| 15 | 
            +
                SECURE_PROTOCOL = 'https'
         | 
| 15 16 |  | 
| 16 17 | 
             
                using StringRefinements
         | 
| 17 18 |  | 
| @@ -33,8 +34,10 @@ module Dolly | |
| 33 34 | 
             
                  request :put, resource.cgi_escape, data
         | 
| 34 35 | 
             
                end
         | 
| 35 36 |  | 
| 36 | 
            -
                def delete resource, rev
         | 
| 37 | 
            -
                   | 
| 37 | 
            +
                def delete resource, rev = nil, escape: true
         | 
| 38 | 
            +
                  query = { query: { rev: rev } } if rev
         | 
| 39 | 
            +
                  resource = resource.cgi_escape if escape
         | 
| 40 | 
            +
                  request :delete, resource, query
         | 
| 38 41 | 
             
                end
         | 
| 39 42 |  | 
| 40 43 | 
             
                def view resource, opts
         | 
| @@ -58,28 +61,35 @@ module Dolly | |
| 58 61 | 
             
                end
         | 
| 59 62 |  | 
| 60 63 | 
             
                def request(method, resource, data = {})
         | 
| 61 | 
            -
                  headers  = Dolly::HeaderRequest.new data | 
| 62 | 
            -
                  uri      = build_uri(resource, data | 
| 64 | 
            +
                  headers  = Dolly::HeaderRequest.new data&.delete(:headers)
         | 
| 65 | 
            +
                  uri      = build_uri(resource, data&.delete(:query))
         | 
| 63 66 | 
             
                  klass    = request_method(method)
         | 
| 64 67 | 
             
                  req      = klass.new(uri, headers)
         | 
| 65 68 | 
             
                  req.body = format_data(data, headers.json?)
         | 
| 66 69 | 
             
                  response = start_request(req)
         | 
| 67 70 |  | 
| 68 | 
            -
                  response_format(response)
         | 
| 71 | 
            +
                  response_format(response, method)
         | 
| 69 72 | 
             
                end
         | 
| 70 73 |  | 
| 71 74 | 
             
                private
         | 
| 72 75 |  | 
| 73 76 | 
             
                def start_request(req)
         | 
| 74 | 
            -
                   | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 77 | 
            -
                   | 
| 77 | 
            +
                  req.basic_auth env['username'], env['password'] if env['username']&.present?
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                  http = Net::HTTP.new(req.uri.host, req.uri.port)
         | 
| 80 | 
            +
                  http.use_ssl = secure?
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                  http.request(req)
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                def secure?
         | 
| 86 | 
            +
                  env['protocol'] == SECURE_PROTOCOL
         | 
| 78 87 | 
             
                end
         | 
| 79 88 |  | 
| 80 | 
            -
                def response_format(res)
         | 
| 89 | 
            +
                def response_format(res, method)
         | 
| 81 90 | 
             
                  raise Dolly::ResourceNotFound if res.code.to_i == 404
         | 
| 82 91 | 
             
                  raise Dolly::ServerError.new(res.body) if (400..600).include? res.code.to_i
         | 
| 92 | 
            +
                  return res if method == :head
         | 
| 83 93 | 
             
                  Oj.load(res.body, symbol_keys: true)
         | 
| 84 94 | 
             
                end
         | 
| 85 95 |  | 
    
        data/lib/dolly/document.rb
    CHANGED
    
    | @@ -1,9 +1,13 @@ | |
| 1 | 
            +
            require 'dolly/mango'
         | 
| 2 | 
            +
            require 'dolly/mango_index'
         | 
| 1 3 | 
             
            require 'dolly/query'
         | 
| 4 | 
            +
            require 'dolly/view_query'
         | 
| 2 5 | 
             
            require 'dolly/connection'
         | 
| 3 6 | 
             
            require 'dolly/request'
         | 
| 4 7 | 
             
            require 'dolly/depracated_database'
         | 
| 5 8 | 
             
            require 'dolly/document_state'
         | 
| 6 9 | 
             
            require 'dolly/properties'
         | 
| 10 | 
            +
            require 'dolly/document_type'
         | 
| 7 11 | 
             
            require 'dolly/identity_properties'
         | 
| 8 12 | 
             
            require 'dolly/attachment'
         | 
| 9 13 | 
             
            require 'dolly/property_manager'
         | 
| @@ -15,11 +19,15 @@ require 'refinements/string_refinements' | |
| 15 19 |  | 
| 16 20 | 
             
            module Dolly
         | 
| 17 21 | 
             
              class Document
         | 
| 22 | 
            +
                extend Mango
         | 
| 18 23 | 
             
                extend Query
         | 
| 24 | 
            +
                extend ViewQuery
         | 
| 19 25 | 
             
                extend Request
         | 
| 20 26 | 
             
                extend DepracatedDatabase
         | 
| 21 27 | 
             
                extend Properties
         | 
| 22 28 | 
             
                extend DocumentCreation
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                include DocumentType
         | 
| 23 31 | 
             
                include PropertyManager
         | 
| 24 32 | 
             
                include Timestamp
         | 
| 25 33 | 
             
                include DocumentState
         | 
| @@ -30,7 +38,8 @@ module Dolly | |
| 30 38 |  | 
| 31 39 | 
             
                attr_writer :doc
         | 
| 32 40 |  | 
| 33 | 
            -
                def initialize | 
| 41 | 
            +
                def initialize(attributes = {})
         | 
| 42 | 
            +
                  init_ancestor_properties
         | 
| 34 43 | 
             
                  properties.each(&build_property(attributes))
         | 
| 35 44 | 
             
                end
         | 
| 36 45 |  | 
| @@ -39,5 +48,16 @@ module Dolly | |
| 39 48 | 
             
                def doc
         | 
| 40 49 | 
             
                  @doc ||= {}
         | 
| 41 50 | 
             
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                def init_ancestor_properties
         | 
| 53 | 
            +
                  self.class.ancestors.map do |ancestor|
         | 
| 54 | 
            +
                    begin
         | 
| 55 | 
            +
                      ancestor.properties.entries.each do |property|
         | 
| 56 | 
            +
                        properties << property
         | 
| 57 | 
            +
                      end
         | 
| 58 | 
            +
                    rescue NoMethodError => e
         | 
| 59 | 
            +
                    end
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
                end
         | 
| 42 62 | 
             
              end
         | 
| 43 63 | 
             
            end
         | 
    
        data/lib/dolly/document_state.rb
    CHANGED
    
    
    
        data/lib/dolly/document_type.rb
    CHANGED
    
    | @@ -13,8 +13,8 @@ module Dolly | |
| 13 13 | 
             
                  "#{name_paramitized}/#{key}"
         | 
| 14 14 | 
             
                end
         | 
| 15 15 |  | 
| 16 | 
            -
                def base_id | 
| 17 | 
            -
                  id.sub(%r{^#{name_paramitized}/}, '')
         | 
| 16 | 
            +
                def base_id
         | 
| 17 | 
            +
                  self.id.sub(%r{^#{name_paramitized}/}, '')
         | 
| 18 18 | 
             
                end
         | 
| 19 19 |  | 
| 20 20 | 
             
                def name_paramitized
         | 
| @@ -24,5 +24,24 @@ module Dolly | |
| 24 24 | 
             
                def class_name
         | 
| 25 25 | 
             
                  is_a?(Class) ? name : self.class.name
         | 
| 26 26 | 
             
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def typed?
         | 
| 29 | 
            +
                  respond_to?(:type)
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def set_type
         | 
| 33 | 
            +
                  return unless typed?
         | 
| 34 | 
            +
                  write_attribute(:type, name_paramitized)
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def self.included(base)
         | 
| 38 | 
            +
                  base.extend(ClassMethods)
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                module ClassMethods
         | 
| 42 | 
            +
                  def typed_model
         | 
| 43 | 
            +
                    property :type, class_name: String
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                end
         | 
| 27 46 | 
             
              end
         | 
| 28 47 | 
             
            end
         | 
    
        data/lib/dolly/exceptions.rb
    CHANGED
    
    | @@ -15,6 +15,17 @@ module Dolly | |
| 15 15 | 
             
                end
         | 
| 16 16 | 
             
              end
         | 
| 17 17 |  | 
| 18 | 
            +
              class InvalidMangoOperatorError < RuntimeError
         | 
| 19 | 
            +
                def initialize msg
         | 
| 20 | 
            +
                  @msg = msg
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def to_s
         | 
| 24 | 
            +
                  "Invalid Mango operator: #{@msg.inspect}"
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              class IndexNotFoundError < RuntimeError; end
         | 
| 18 29 | 
             
              class InvalidConfigFileError < RuntimeError; end
         | 
| 19 30 | 
             
              class InvalidProperty < RuntimeError; end
         | 
| 20 31 | 
             
              class DocumentInvalidError < RuntimeError; end
         | 
    
        data/lib/dolly/mango.rb
    ADDED
    
    | @@ -0,0 +1,124 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'refinements/hash_refinements'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Dolly
         | 
| 6 | 
            +
              module Mango
         | 
| 7 | 
            +
                using HashRefinements
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                SELECTOR_SYMBOL = '$'
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                COMBINATION_OPERATORS = %I[
         | 
| 12 | 
            +
                  and
         | 
| 13 | 
            +
                  or
         | 
| 14 | 
            +
                  not
         | 
| 15 | 
            +
                  nor
         | 
| 16 | 
            +
                  all
         | 
| 17 | 
            +
                  elemMatch
         | 
| 18 | 
            +
                  allMath
         | 
| 19 | 
            +
                ].freeze
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                CONDITION_OPERATORS = %I[
         | 
| 22 | 
            +
                  lt
         | 
| 23 | 
            +
                  lte
         | 
| 24 | 
            +
                  eq
         | 
| 25 | 
            +
                  ne
         | 
| 26 | 
            +
                  gte
         | 
| 27 | 
            +
                  gt
         | 
| 28 | 
            +
                  exists
         | 
| 29 | 
            +
                  in
         | 
| 30 | 
            +
                  nin
         | 
| 31 | 
            +
                  size
         | 
| 32 | 
            +
                  mod
         | 
| 33 | 
            +
                  regex
         | 
| 34 | 
            +
                ].freeze
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                TYPE_OPERATOR = %I[
         | 
| 37 | 
            +
                  type
         | 
| 38 | 
            +
                  $type
         | 
| 39 | 
            +
                ]
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                ALL_OPERATORS = COMBINATION_OPERATORS + CONDITION_OPERATORS
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                DESIGN = '_find'
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def find_by(query, opts = {})
         | 
| 46 | 
            +
                  build_model_from_doc(find_doc_by(query, opts))
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def find_doc_by(query, opts = {})
         | 
| 50 | 
            +
                  raise Dolly::IndexNotFoundError unless index_exists?(query)
         | 
| 51 | 
            +
                  opts.merge!(limit: 1)
         | 
| 52 | 
            +
                  perform_query(build_query(query, opts))[:docs].first
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def where(query, opts = {})
         | 
| 56 | 
            +
                  docs_where(query, opts).map do |doc|
         | 
| 57 | 
            +
                    build_model_from_doc(doc)
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                def docs_where(query, opts = {})
         | 
| 62 | 
            +
                  raise Dolly::IndexNotFoundError unless index_exists?(query)
         | 
| 63 | 
            +
                  perform_query(build_query(query, opts))[:docs]
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                private
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                def build_model_from_doc(doc)
         | 
| 69 | 
            +
                  return nil if doc.nil?
         | 
| 70 | 
            +
                  new(doc.slice(*all_property_keys))
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                def perform_query(structured_query)
         | 
| 74 | 
            +
                  connection.post(DESIGN, structured_query)
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                def build_query(query, opts)
         | 
| 78 | 
            +
                  { 'selector' => build_selectors(query) }.merge(opts)
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                def build_selectors(query)
         | 
| 82 | 
            +
                  query.deep_transform_keys do |key|
         | 
| 83 | 
            +
                    next build_key(key) if is_operator?(key)
         | 
| 84 | 
            +
                    next key if is_type_operator?(key)
         | 
| 85 | 
            +
                    raise Dolly::InvalidMangoOperatorError.new(key) unless has_property?(key)
         | 
| 86 | 
            +
                    key
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                def build_key(key)
         | 
| 91 | 
            +
                  "#{SELECTOR_SYMBOL}#{key}"
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                def is_operator?(key)
         | 
| 95 | 
            +
                  ALL_OPERATORS.include?(key)
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                def index_exists?(query)
         | 
| 99 | 
            +
                  Dolly::MangoIndex.find_by_fields(fetch_fields(query))
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                def fetch_fields(query)
         | 
| 103 | 
            +
                  deep_keys(query).reject do |key|
         | 
| 104 | 
            +
                    is_operator?(key) || is_type_operator?(key)
         | 
| 105 | 
            +
                  end
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                def has_property?(key)
         | 
| 109 | 
            +
                  self.all_property_keys.include?(key)
         | 
| 110 | 
            +
                end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                def is_type_operator?(key)
         | 
| 113 | 
            +
                  TYPE_OPERATOR.include?(key.to_sym)
         | 
| 114 | 
            +
                end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                def deep_keys(obj)
         | 
| 117 | 
            +
                  case obj
         | 
| 118 | 
            +
                  when Hash then obj.keys + obj.values.flat_map { |v| deep_keys(v) }
         | 
| 119 | 
            +
                  when Array then obj.flat_map { |i| deep_keys(i) }
         | 
| 120 | 
            +
                  else []
         | 
| 121 | 
            +
                  end
         | 
| 122 | 
            +
                end
         | 
| 123 | 
            +
              end
         | 
| 124 | 
            +
            end
         | 
| @@ -0,0 +1,65 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'forwardable'
         | 
| 4 | 
            +
            require 'dolly/document'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Dolly
         | 
| 7 | 
            +
              class MangoIndex
         | 
| 8 | 
            +
                class << self
         | 
| 9 | 
            +
                  extend Forwardable
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  ALL_DOCS = '_all_docs'
         | 
| 12 | 
            +
                  DESIGN = '_index'
         | 
| 13 | 
            +
                  ROWS_KEY = :rows
         | 
| 14 | 
            +
                  DESIGN_PREFIX = '_design/'
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def_delegators :connection, :get, :post
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def all
         | 
| 19 | 
            +
                    get(DESIGN)[:indexes]
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def create(name, fields, type = 'json')
         | 
| 23 | 
            +
                    post(DESIGN, build_index_structure(name, fields, type))
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def find_by_fields(fields)
         | 
| 27 | 
            +
                    rows = get(ALL_DOCS, key: key_from_fields(fields))[ROWS_KEY]
         | 
| 28 | 
            +
                    rows && rows.any?
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def delete_all
         | 
| 32 | 
            +
                    all.each do |index_doc|
         | 
| 33 | 
            +
                      next if index_doc[:ddoc].nil?
         | 
| 34 | 
            +
                      delete(index_doc)
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def delete(index_doc)
         | 
| 39 | 
            +
                    resource = "#{DESIGN}/#{index_doc[:ddoc]}/json/#{index_doc[:name]}"
         | 
| 40 | 
            +
                    connection.delete(resource, escape: false)
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  private
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  def connection
         | 
| 46 | 
            +
                    @connection ||= Dolly::Document.connection
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  def build_index_structure(name, fields, type)
         | 
| 50 | 
            +
                    {
         | 
| 51 | 
            +
                      ddoc: key_from_fields(fields).gsub(DESIGN_PREFIX, ''),
         | 
| 52 | 
            +
                      index: {
         | 
| 53 | 
            +
                        fields: fields
         | 
| 54 | 
            +
                      },
         | 
| 55 | 
            +
                      name: name,
         | 
| 56 | 
            +
                      type: type
         | 
| 57 | 
            +
                    }
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  def key_from_fields(fields)
         | 
| 61 | 
            +
                    "#{DESIGN_PREFIX}index_#{fields.join('_')}"
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
            end
         | 
    
        data/lib/dolly/properties.rb
    CHANGED
    
    | @@ -7,6 +7,7 @@ module Dolly | |
| 7 7 |  | 
| 8 8 | 
             
                def property *opts, class_name: nil, default: nil
         | 
| 9 9 | 
             
                  opts.each do |opt|
         | 
| 10 | 
            +
             | 
| 10 11 | 
             
                    properties << (prop = Property.new(opt, class_name, default))
         | 
| 11 12 | 
             
                    send(:attr_reader, opt)
         | 
| 12 13 |  | 
| @@ -20,8 +21,12 @@ module Dolly | |
| 20 21 | 
             
                  @properties ||= PropertySet.new
         | 
| 21 22 | 
             
                end
         | 
| 22 23 |  | 
| 24 | 
            +
                def all_property_keys
         | 
| 25 | 
            +
                  properties.map(&:key) + SPECIAL_KEYS
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 23 28 | 
             
                def property_keys
         | 
| 24 | 
            -
                   | 
| 29 | 
            +
                  all_property_keys - SPECIAL_KEYS
         | 
| 25 30 | 
             
                end
         | 
| 26 31 |  | 
| 27 32 | 
             
                def property_clean_doc(doc)
         | 
    
        data/lib/dolly/property_set.rb
    CHANGED
    
    
    
        data/lib/dolly/query.rb
    CHANGED
    
    | @@ -1,14 +1,17 @@ | |
| 1 1 | 
             
            require 'dolly/collection'
         | 
| 2 2 | 
             
            require 'dolly/query_arguments'
         | 
| 3 3 | 
             
            require 'dolly/document_type'
         | 
| 4 | 
            +
            require 'refinements/string_refinements'
         | 
| 4 5 |  | 
| 5 6 | 
             
            module Dolly
         | 
| 6 7 | 
             
              module Query
         | 
| 7 8 | 
             
                include QueryArguments
         | 
| 8 9 | 
             
                include DocumentType
         | 
| 9 10 |  | 
| 11 | 
            +
                using StringRefinements
         | 
| 12 | 
            +
             | 
| 10 13 | 
             
                def find *keys
         | 
| 11 | 
            -
                  query_hash = { keys: namespace_keys(keys) }
         | 
| 14 | 
            +
                  query_hash = { keys: namespace_keys(keys).map { |k| k.cgi_escape } }
         | 
| 12 15 |  | 
| 13 16 | 
             
                  build_collection(query_hash).first_or_all&.itself ||
         | 
| 14 17 | 
             
                    raise(Dolly::ResourceNotFound)
         | 
| @@ -38,16 +41,11 @@ module Dolly | |
| 38 41 | 
             
                  opts          = opts.each_with_object({}) { |(k, v), h| h[k] = escape_value(v) }
         | 
| 39 42 | 
             
                  query_results = raw_view(doc, view_name, opts)
         | 
| 40 43 |  | 
| 41 | 
            -
                  Collection.new(query_results).first_or_all
         | 
| 42 | 
            -
                end
         | 
| 43 | 
            -
             | 
| 44 | 
            -
                def raw_view doc, view_name, opts = {}
         | 
| 45 | 
            -
                  design = "_design/#{doc}/_view/#{view_name}"
         | 
| 46 | 
            -
                  connection.view(design, opts)
         | 
| 44 | 
            +
                  Collection.new({ rows: query_results, options: {} }).first_or_all
         | 
| 47 45 | 
             
                end
         | 
| 48 46 |  | 
| 49 47 | 
             
                def build_collection(query)
         | 
| 50 | 
            -
                  Collection.new(connection.get('_all_docs', query.merge(include_docs: true)))
         | 
| 48 | 
            +
                  Collection.new({ rows: connection.get('_all_docs', query.merge(include_docs: true)), options: { doc_type: self.class_name }})
         | 
| 51 49 | 
             
                end
         | 
| 52 50 |  | 
| 53 51 | 
             
                def bulk_document
         | 
    
        data/lib/dolly/request.rb
    CHANGED
    
    
    
        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
         | 
    
        data/lib/tasks/db.rake
    CHANGED
    
    | @@ -1,3 +1,5 @@ | |
| 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
         | 
| @@ -40,7 +42,7 @@ namespace :db do | |
| 40 42 | 
             
                  begin
         | 
| 41 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'
         | 
| @@ -59,5 +61,18 @@ namespace :db do | |
| 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
         | 
| 63 78 |  | 
    
        data/test/document_test.rb
    CHANGED
    
    | @@ -1,7 +1,5 @@ | |
| 1 1 | 
             
            require 'test_helper'
         | 
| 2 2 |  | 
| 3 | 
            -
            class BaseDolly < Dolly::Document; end
         | 
| 4 | 
            -
             | 
| 5 3 | 
             
            class BarFoo < BaseDolly
         | 
| 6 4 | 
             
              property :a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, :n, :persist
         | 
| 7 5 | 
             
            end
         | 
| @@ -54,7 +52,6 @@ class Bar < FooBar | |
| 54 52 | 
             
            end
         | 
| 55 53 |  | 
| 56 54 | 
             
            class DocumentTest < Test::Unit::TestCase
         | 
| 57 | 
            -
              DB_BASE_PATH = "http://localhost:5984/test".freeze
         | 
| 58 55 |  | 
| 59 56 | 
             
              def setup
         | 
| 60 57 | 
             
                data     = {foo: 'Foo', bar: 'Bar', type: 'foo_bar'}
         | 
| @@ -578,50 +575,4 @@ class DocumentTest < Test::Unit::TestCase | |
| 578 575 | 
             
                assert bar = Bar.new(a: 1)
         | 
| 579 576 | 
             
                assert_equal 1, bar.a
         | 
| 580 577 | 
             
              end
         | 
| 581 | 
            -
             | 
| 582 | 
            -
              private
         | 
| 583 | 
            -
              def generic_response rows, count = 1
         | 
| 584 | 
            -
                {total_rows: count, offset:0, rows: rows}
         | 
| 585 | 
            -
              end
         | 
| 586 | 
            -
             | 
| 587 | 
            -
              def build_view_response properties
         | 
| 588 | 
            -
                rows = properties.map.with_index do |v, i|
         | 
| 589 | 
            -
                  {
         | 
| 590 | 
            -
                    id: "foo_bar/#{i}",
         | 
| 591 | 
            -
                    key: "foo_bar",
         | 
| 592 | 
            -
                    value: 1,
         | 
| 593 | 
            -
                    doc: {_id: "foo_bar/#{i}", _rev: SecureRandom.hex}.merge!(v)
         | 
| 594 | 
            -
                  }
         | 
| 595 | 
            -
                end
         | 
| 596 | 
            -
                generic_response rows, properties.count
         | 
| 597 | 
            -
              end
         | 
| 598 | 
            -
             | 
| 599 | 
            -
              def build_view_collation_response properties
         | 
| 600 | 
            -
                rows = properties.map.with_index do |v, i|
         | 
| 601 | 
            -
                  id = i.zero? ? "foo_bar/#{i}" : "baz/#{i}"
         | 
| 602 | 
            -
                  {
         | 
| 603 | 
            -
                    id: id,
         | 
| 604 | 
            -
                    key: "foo_bar",
         | 
| 605 | 
            -
                    value: 1,
         | 
| 606 | 
            -
                    doc: {_id: id, _rev: SecureRandom.hex}.merge!(v)
         | 
| 607 | 
            -
                  }
         | 
| 608 | 
            -
                end
         | 
| 609 | 
            -
                generic_response rows, properties.count
         | 
| 610 | 
            -
              end
         | 
| 611 | 
            -
             | 
| 612 | 
            -
             | 
| 613 | 
            -
              def build_request keys, body, view_name = 'foo_bar'
         | 
| 614 | 
            -
                query = "keys=#{CGI::escape keys.to_s.gsub(' ','')}&" unless keys&.empty?
         | 
| 615 | 
            -
                stub_request(:get, "#{query_base_path}?#{query.to_s}include_docs=true").
         | 
| 616 | 
            -
                  to_return(body: body.to_json)
         | 
| 617 | 
            -
              end
         | 
| 618 | 
            -
             | 
| 619 | 
            -
              def query_base_path
         | 
| 620 | 
            -
                "#{DB_BASE_PATH}/_all_docs"
         | 
| 621 | 
            -
              end
         | 
| 622 | 
            -
             | 
| 623 | 
            -
              def build_save_request(obj)
         | 
| 624 | 
            -
                stub_request(:put, "#{DB_BASE_PATH}/#{CGI.escape(obj.id)}").
         | 
| 625 | 
            -
                  to_return(body: {ok: true, id: obj.id, rev: "FF0000" }.to_json)
         | 
| 626 | 
            -
              end
         | 
| 627 578 | 
             
            end
         | 
| @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            require 'test_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class TypedDoc < Dolly::Document
         | 
| 4 | 
            +
              typed_model
         | 
| 5 | 
            +
            end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            class UntypedDoc < Dolly::Document
         | 
| 8 | 
            +
            end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            class DocumentTypeTest < Test::Unit::TestCase
         | 
| 11 | 
            +
              test 'typed?' do
         | 
| 12 | 
            +
                assert_equal(TypedDoc.new.typed?, true)
         | 
| 13 | 
            +
                assert_equal(UntypedDoc.new.typed?, false)
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              test 'typed_model' do
         | 
| 17 | 
            +
                assert_equal(TypedDoc.new.type, nil)
         | 
| 18 | 
            +
                assert_equal(UntypedDoc.new.respond_to?(:type), false)
         | 
| 19 | 
            +
                assert_raise NoMethodError do
         | 
| 20 | 
            +
                  UntypedDoc.new.type
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              test 'set_type' do
         | 
| 25 | 
            +
                assert_equal(TypedDoc.new.set_type, TypedDoc.name_paramitized)
         | 
| 26 | 
            +
                assert_equal(UntypedDoc.new.set_type, nil)
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
            end
         | 
| @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            require 'test_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class BaseDoc < Dolly::Document
         | 
| 4 | 
            +
              typed_model
         | 
| 5 | 
            +
            end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            class BaseBaseDoc < BaseDoc
         | 
| 8 | 
            +
              property :supertype
         | 
| 9 | 
            +
            end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            class NewBar < BaseBaseDoc
         | 
| 12 | 
            +
              property :a, :b
         | 
| 13 | 
            +
            end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            class InheritanceTest < Test::Unit::TestCase
         | 
| 16 | 
            +
              test 'property inheritance' do
         | 
| 17 | 
            +
                assert_equal(BaseBaseDoc.new.properties.map(&:key), [:supertype, :type])
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              test 'deep properties inheritance' do
         | 
| 21 | 
            +
                assert_equal(NewBar.new.properties.map(&:key), [:a, :b, :supertype, :type])
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
            end
         | 
| @@ -0,0 +1,64 @@ | |
| 1 | 
            +
            require 'test_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class FooBar < BaseDolly
         | 
| 4 | 
            +
              property :foo, :bar
         | 
| 5 | 
            +
              property :with_default, default: 1
         | 
| 6 | 
            +
              property :boolean, class_name: TrueClass, default: true
         | 
| 7 | 
            +
              property :date, class_name: Date
         | 
| 8 | 
            +
              property :time, class_name: Time
         | 
| 9 | 
            +
              property :datetime, class_name: DateTime
         | 
| 10 | 
            +
              property :is_nil, class_name: NilClass, default: nil
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              timestamps!
         | 
| 13 | 
            +
            end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            class MangoIndexTest < Test::Unit::TestCase
         | 
| 16 | 
            +
              DB_BASE_PATH = "http://localhost:5984/test".freeze
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              def setup
         | 
| 19 | 
            +
                stub_request(:get, index_base_path).
         | 
| 20 | 
            +
                  to_return(body: { indexes:[ {
         | 
| 21 | 
            +
                    ddoc: nil,
         | 
| 22 | 
            +
                    name:"_all_docs",
         | 
| 23 | 
            +
                    type:"special",
         | 
| 24 | 
            +
                    def:{ fields:[{ _id:"asc" }] }
         | 
| 25 | 
            +
                  },
         | 
| 26 | 
            +
                  {
         | 
| 27 | 
            +
                    ddoc: "_design/1",
         | 
| 28 | 
            +
                    name:"foo-index-json",
         | 
| 29 | 
            +
                    type:"json",
         | 
| 30 | 
            +
                    def:{ fields:[{ foo:"asc" }] }
         | 
| 31 | 
            +
                  }
         | 
| 32 | 
            +
                ]}.to_json)
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              test '#delete_all' do
         | 
| 36 | 
            +
                previous_indexes = Dolly::MangoIndex.all
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                stub_request(:delete, index_delete_path(previous_indexes.last)).
         | 
| 39 | 
            +
                  to_return(body: { "ok": true }.to_json)
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                Dolly::MangoIndex.delete_all
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                stub_request(:get, index_base_path).
         | 
| 44 | 
            +
                  to_return(body: { indexes:[ {
         | 
| 45 | 
            +
                    ddoc: nil,
         | 
| 46 | 
            +
                    name:"_all_docs",
         | 
| 47 | 
            +
                    type:"special",
         | 
| 48 | 
            +
                    def:{ fields:[{ _id:"asc" }] }
         | 
| 49 | 
            +
                  }
         | 
| 50 | 
            +
                ]}.to_json)
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                new_indexes = Dolly::MangoIndex.all
         | 
| 53 | 
            +
                assert_not_equal(new_indexes.length, previous_indexes.length)
         | 
| 54 | 
            +
                assert_equal(new_indexes.length, 1)
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
              def index_base_path
         | 
| 58 | 
            +
                "#{DB_BASE_PATH}/_index"
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
              def index_delete_path(doc)
         | 
| 62 | 
            +
                "#{index_base_path}/#{doc[:ddoc]}/json/#{doc[:name]}"
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
            end
         | 
    
        data/test/mango_test.rb
    ADDED
    
    | @@ -0,0 +1,273 @@ | |
| 1 | 
            +
            require 'test_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class FooBar < BaseDolly
         | 
| 4 | 
            +
              property :foo, :bar
         | 
| 5 | 
            +
              property :with_default, default: 1
         | 
| 6 | 
            +
              property :boolean, class_name: TrueClass, default: true
         | 
| 7 | 
            +
              property :date, class_name: Date
         | 
| 8 | 
            +
              property :time, class_name: Time
         | 
| 9 | 
            +
              property :datetime, class_name: DateTime
         | 
| 10 | 
            +
              property :is_nil, class_name: NilClass, default: nil
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              timestamps!
         | 
| 13 | 
            +
            end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            class FooBarTyped < BaseDolly
         | 
| 16 | 
            +
              typed_model
         | 
| 17 | 
            +
            end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            class MangoTest < Test::Unit::TestCase
         | 
| 20 | 
            +
              DB_BASE_PATH = "http://localhost:5984/test".freeze
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              def setup
         | 
| 23 | 
            +
                data     = {foo: 'Foo', bar: 'Bar', type: 'foo_bar'}
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                all_docs = [ {foo: 'Foo B', bar: 'Bar B', type: 'foo_bar'},  {foo: 'Foo A', bar: 'Bar A', type: 'foo_bar'}]
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                view_resp   = build_view_response [data]
         | 
| 28 | 
            +
                empty_resp  =  build_view_response []
         | 
| 29 | 
            +
                not_found_resp = generic_response [{ key: "foo_bar/2", error: "not_found" }]
         | 
| 30 | 
            +
                @multi_resp = build_view_response all_docs
         | 
| 31 | 
            +
                @multi_type_resp = build_view_collation_response all_docs
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                build_request [["foo_bar","1"]], view_resp
         | 
| 34 | 
            +
                build_request [["foo_bar","2"]], empty_resp
         | 
| 35 | 
            +
                build_request [["foo_bar","1"],["foo_bar","2"]], @multi_resp
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                stub_request(:get, "#{query_base_path}?startkey=%22foo_bar%2F%22&endkey=%22foo_bar%2F%EF%BF%B0%22&include_docs=true").
         | 
| 38 | 
            +
                  to_return(body: @multi_resp.to_json)
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                stub_request(:get, "#{all_docs_path}?key=\"index_foo\"").
         | 
| 41 | 
            +
                  to_return(body: {
         | 
| 42 | 
            +
                    total_rows: 2,
         | 
| 43 | 
            +
                    offset: 0,
         | 
| 44 | 
            +
                    rows: [{
         | 
| 45 | 
            +
                      id: '_design/index_foo',
         | 
| 46 | 
            +
                      key: '_design/index_foo',
         | 
| 47 | 
            +
                      value: { rev: '1-c5457a0d26da85f15c4ad6bd739e441d' }
         | 
| 48 | 
            +
                    }]}.to_json)
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                stub_request(:get, "#{all_docs_path}?key=\"index_date\"").
         | 
| 51 | 
            +
                  to_return(body: {
         | 
| 52 | 
            +
                    total_rows: 2,
         | 
| 53 | 
            +
                    offset: 0,
         | 
| 54 | 
            +
                    rows: []}.to_json)
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
              test '#find_by' do
         | 
| 58 | 
            +
                #TODO: clean up all the fake request creation
         | 
| 59 | 
            +
                resp = { docs: [{ foo: 'bar', id: "foo_bar/1"} ] }
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                stub_request(:post, query_base_path).
         | 
| 62 | 
            +
                 to_return(body: resp.to_json)
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                key = 'foo'
         | 
| 65 | 
            +
                stub_request(:get, "#{all_docs_path}?key=\"_design/index_#{key}\"").
         | 
| 66 | 
            +
                 to_return(body: index_response(key).to_json)
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                assert_equal(FooBar.find_by(foo: 'bar').class, FooBar)
         | 
| 69 | 
            +
              end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
              test '#find_by for a property that does not have an index' do
         | 
| 72 | 
            +
                #TODO: clean up all the fake request creation
         | 
| 73 | 
            +
                resp = { docs: [{ foo: 'bar', id: "foo_bar/1"} ] }
         | 
| 74 | 
            +
                key = 'date'
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                stub_request(:post, query_base_path).
         | 
| 77 | 
            +
                 to_return(body: resp.to_json)
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                stub_request(:get, "#{all_docs_path}?key=\"_design/index_#{key}\"").
         | 
| 80 | 
            +
                 to_return(body: { rows: [] }.to_json)
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                assert_raise Dolly::IndexNotFoundError do
         | 
| 83 | 
            +
                  FooBar.find_by(date: Date.today)
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
              end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
              test '#find_by with no returned data' do
         | 
| 88 | 
            +
                resp = { docs: [] }
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                stub_request(:post, query_base_path).
         | 
| 91 | 
            +
                 to_return(body: resp.to_json)
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                key = 'foo'
         | 
| 94 | 
            +
                stub_request(:get, "#{all_docs_path}?key=\"_design/index_#{key}\"").
         | 
| 95 | 
            +
                 to_return(body: index_response(key).to_json)
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                assert_equal(FooBar.find_by(foo: 'bar'), nil)
         | 
| 98 | 
            +
              end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
              test '#find_doc_by' do
         | 
| 101 | 
            +
                #TODO: clean up all the fake request creation
         | 
| 102 | 
            +
                resp = { docs: [{ foo: 'bar', id: "foo_bar/1"} ] }
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                stub_request(:post, query_base_path).
         | 
| 105 | 
            +
                 to_return(body: resp.to_json)
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                key = 'foo'
         | 
| 108 | 
            +
                stub_request(:get, "#{all_docs_path}?key=\"_design/index_#{key}\"").
         | 
| 109 | 
            +
                 to_return(body: index_response(key).to_json)
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                assert_equal(FooBar.find_doc_by(foo: 'bar').class, Hash)
         | 
| 112 | 
            +
              end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
              test '#where' do
         | 
| 115 | 
            +
                #TODO: clean up all the fake request creation
         | 
| 116 | 
            +
                resp = { docs: [{ foo: 'bar', id: "foo_bar/1"} ] }
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                stub_request(:post, query_base_path).
         | 
| 119 | 
            +
                 to_return(body: resp.to_json)
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                key = 'foo'
         | 
| 122 | 
            +
                stub_request(:get, "#{all_docs_path}?key=\"_design/index_#{key}\"").
         | 
| 123 | 
            +
                 to_return(body: index_response(key).to_json)
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                assert_equal(FooBar.where(foo: { eq: 'bar' }).map(&:class).uniq, [FooBar])
         | 
| 126 | 
            +
              end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
              test '#where for a property that does not have an index' do
         | 
| 129 | 
            +
                #TODO: clean up all the fake request creation
         | 
| 130 | 
            +
                resp = { docs: [{ foo: 'bar', id: "foo_bar/1"} ] }
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                stub_request(:post, query_base_path).
         | 
| 133 | 
            +
                 to_return(body: resp.to_json)
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                key = 'date'
         | 
| 136 | 
            +
                stub_request(:get, "#{all_docs_path}?key=\"_design/index_#{key}\"").
         | 
| 137 | 
            +
                 to_return(body: { rows: [] }.to_json)
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                assert_raise Dolly::IndexNotFoundError do
         | 
| 140 | 
            +
                  FooBar.where(date: Date.today)
         | 
| 141 | 
            +
                end
         | 
| 142 | 
            +
              end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
              test '#where with no returned data' do
         | 
| 145 | 
            +
                resp = { docs: [] }
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                stub_request(:post, query_base_path).
         | 
| 148 | 
            +
                 to_return(body: resp.to_json)
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                key = 'foo'
         | 
| 151 | 
            +
                stub_request(:get, "#{all_docs_path}?key=\"_design/index_#{key}\"").
         | 
| 152 | 
            +
                 to_return(body: index_response(key).to_json)
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                assert_equal(FooBar.where(foo: 'bar'), [])
         | 
| 155 | 
            +
              end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
              test '#docs_where' do
         | 
| 158 | 
            +
                #TODO: clean up all the fake request creation
         | 
| 159 | 
            +
                resp = { docs: [{ foo: 'bar', id: "foo_bar/1"} ] }
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                stub_request(:post, query_base_path).
         | 
| 162 | 
            +
                 to_return(body: resp.to_json)
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                key = 'foo'
         | 
| 165 | 
            +
                stub_request(:get, "#{all_docs_path}?key=\"_design/index_#{key}\"").
         | 
| 166 | 
            +
                 to_return(body: index_response(key).to_json)
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                assert_equal(FooBar.docs_where(foo: { eq: 'bar' }).map(&:class).uniq, [Hash])
         | 
| 169 | 
            +
              end
         | 
| 170 | 
            +
             | 
| 171 | 
            +
              test '#build_query' do
         | 
| 172 | 
            +
                query = { and: [{ _id: { eq: 'foo_bar/1' } } , { foo: { eq: 'bar'}} ] }
         | 
| 173 | 
            +
                opts = {}
         | 
| 174 | 
            +
                expected = {"selector"=>{"$and"=>[{:_id=>{"$eq"=>"foo_bar/1"}}, {:foo=>{"$eq"=>"bar"}}]}}
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                assert_equal(FooBar.send(:build_query, query, opts), expected)
         | 
| 177 | 
            +
              end
         | 
| 178 | 
            +
             | 
| 179 | 
            +
              test '#build_query with options' do
         | 
| 180 | 
            +
                query = { and: [{ _id: { eq: 'foo_bar/1' } } , { foo: { eq: 'bar'}} ] }
         | 
| 181 | 
            +
                opts = { limit: 1, fields: ['foo']}
         | 
| 182 | 
            +
                expected = {"selector"=>{"$and"=>[{:_id=>{"$eq"=>"foo_bar/1"}}, {:foo=>{"$eq"=>"bar"}}]}, limit: 1, fields: ['foo']}
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                assert_equal(FooBar.send(:build_query, query, opts), expected)
         | 
| 185 | 
            +
              end
         | 
| 186 | 
            +
             | 
| 187 | 
            +
              test '#build_selectors with invalid operator' do
         | 
| 188 | 
            +
                query = { _id: { eeeq: 'foo_bar/1' } }
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                assert_raise Dolly::InvalidMangoOperatorError do
         | 
| 191 | 
            +
                  FooBar.send(:build_selectors, query)
         | 
| 192 | 
            +
                end
         | 
| 193 | 
            +
              end
         | 
| 194 | 
            +
             | 
| 195 | 
            +
              test '#build_selectors with type operator' do
         | 
| 196 | 
            +
                query = { _id: { type: "user" } }
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                assert_nothing_raised Dolly::InvalidMangoOperatorError do
         | 
| 199 | 
            +
                  FooBarTyped.send(:build_selectors, query)
         | 
| 200 | 
            +
                end
         | 
| 201 | 
            +
              end
         | 
| 202 | 
            +
             | 
| 203 | 
            +
              test '#build_selectors with $type operator' do
         | 
| 204 | 
            +
                query = { _id: { "$type" => "null" } }
         | 
| 205 | 
            +
             | 
| 206 | 
            +
                assert_nothing_raised Dolly::InvalidMangoOperatorError do
         | 
| 207 | 
            +
                  FooBarTyped.send(:build_selectors, query)
         | 
| 208 | 
            +
                end
         | 
| 209 | 
            +
              end
         | 
| 210 | 
            +
             | 
| 211 | 
            +
              private
         | 
| 212 | 
            +
             | 
| 213 | 
            +
              def generic_response rows, count = 1
         | 
| 214 | 
            +
                {total_rows: count, offset:0, rows: rows}
         | 
| 215 | 
            +
              end
         | 
| 216 | 
            +
             | 
| 217 | 
            +
              def build_view_response properties
         | 
| 218 | 
            +
                rows = properties.map.with_index do |v, i|
         | 
| 219 | 
            +
                  {
         | 
| 220 | 
            +
                    id: "foo_bar/#{i}",
         | 
| 221 | 
            +
                    key: "foo_bar",
         | 
| 222 | 
            +
                    value: 1,
         | 
| 223 | 
            +
                    doc: {_id: "foo_bar/#{i}", _rev: SecureRandom.hex}.merge!(v)
         | 
| 224 | 
            +
                  }
         | 
| 225 | 
            +
                end
         | 
| 226 | 
            +
                generic_response rows, properties.count
         | 
| 227 | 
            +
              end
         | 
| 228 | 
            +
             | 
| 229 | 
            +
              def build_view_collation_response properties
         | 
| 230 | 
            +
                rows = properties.map.with_index do |v, i|
         | 
| 231 | 
            +
                  id = i.zero? ? "foo_bar/#{i}" : "baz/#{i}"
         | 
| 232 | 
            +
                  {
         | 
| 233 | 
            +
                    id: id,
         | 
| 234 | 
            +
                    key: "foo_bar",
         | 
| 235 | 
            +
                    value: 1,
         | 
| 236 | 
            +
                    doc: {_id: id, _rev: SecureRandom.hex}.merge!(v)
         | 
| 237 | 
            +
                  }
         | 
| 238 | 
            +
                end
         | 
| 239 | 
            +
                generic_response rows, properties.count
         | 
| 240 | 
            +
              end
         | 
| 241 | 
            +
             | 
| 242 | 
            +
             | 
| 243 | 
            +
              def build_request keys, body, view_name = 'foo_bar'
         | 
| 244 | 
            +
                query = "keys=#{CGI::escape keys.to_s.gsub(' ','')}&" unless keys&.empty?
         | 
| 245 | 
            +
                stub_request(:get, "#{query_base_path}?#{query.to_s}include_docs=true").
         | 
| 246 | 
            +
                  to_return(body: body.to_json)
         | 
| 247 | 
            +
              end
         | 
| 248 | 
            +
             | 
| 249 | 
            +
              def query_base_path
         | 
| 250 | 
            +
                "#{DB_BASE_PATH}/_find"
         | 
| 251 | 
            +
              end
         | 
| 252 | 
            +
             | 
| 253 | 
            +
              def all_docs_path
         | 
| 254 | 
            +
                "#{DB_BASE_PATH}/_all_docs"
         | 
| 255 | 
            +
              end
         | 
| 256 | 
            +
             | 
| 257 | 
            +
              def build_save_request(obj)
         | 
| 258 | 
            +
                stub_request(:put, "#{DB_BASE_PATH}/#{CGI.escape(obj.id)}").
         | 
| 259 | 
            +
                  to_return(body: {ok: true, id: obj.id, rev: "FF0000" }.to_json)
         | 
| 260 | 
            +
              end
         | 
| 261 | 
            +
             | 
| 262 | 
            +
              def index_response(key)
         | 
| 263 | 
            +
                {
         | 
| 264 | 
            +
                  rows: [
         | 
| 265 | 
            +
                    {
         | 
| 266 | 
            +
                      id: "_design/index_#{key}",
         | 
| 267 | 
            +
                      key: "_design/index_#{key}",
         | 
| 268 | 
            +
                      value: { rev: '1-c5457a0d26da85f15c4ad6bd739e441d' }
         | 
| 269 | 
            +
                    }
         | 
| 270 | 
            +
                  ]
         | 
| 271 | 
            +
                }
         | 
| 272 | 
            +
              end
         | 
| 273 | 
            +
            end
         | 
    
        data/test/test_helper.rb
    CHANGED
    
    | @@ -13,6 +13,7 @@ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } | |
| 13 13 |  | 
| 14 14 | 
             
            class Test::Unit::TestCase
         | 
| 15 15 | 
             
              DEFAULT_DB = 'test'
         | 
| 16 | 
            +
              DB_BASE_PATH = "http://localhost:5984/test".freeze
         | 
| 16 17 |  | 
| 17 18 | 
             
              setup :global_setup
         | 
| 18 19 |  | 
| @@ -28,4 +29,54 @@ class Test::Unit::TestCase | |
| 28 29 | 
             
              def base_path
         | 
| 29 30 | 
             
                %r{http://.*:5984/#{DEFAULT_DB}}
         | 
| 30 31 | 
             
              end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              def generic_response rows, count = 1
         | 
| 34 | 
            +
                {total_rows: count, offset:0, rows: rows}
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              def build_view_response properties
         | 
| 38 | 
            +
                rows = properties.map.with_index do |v, i|
         | 
| 39 | 
            +
                  {
         | 
| 40 | 
            +
                    id: "foo_bar/#{i}",
         | 
| 41 | 
            +
                    key: "foo_bar",
         | 
| 42 | 
            +
                    value: 1,
         | 
| 43 | 
            +
                    doc: {_id: "foo_bar/#{i}", _rev: SecureRandom.hex}.merge!(v)
         | 
| 44 | 
            +
                  }
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
                generic_response rows, properties.count
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
              def build_view_collation_response properties
         | 
| 50 | 
            +
                rows = properties.map.with_index do |v, i|
         | 
| 51 | 
            +
                  id = i.zero? ? "foo_bar/#{i}" : "baz/#{i}"
         | 
| 52 | 
            +
                  {
         | 
| 53 | 
            +
                    id: id,
         | 
| 54 | 
            +
                    key: "foo_bar",
         | 
| 55 | 
            +
                    value: 1,
         | 
| 56 | 
            +
                    doc_type: id.split("/").first,
         | 
| 57 | 
            +
                    doc: {
         | 
| 58 | 
            +
                      _id: id, _rev: SecureRandom.hex
         | 
| 59 | 
            +
                    }.merge!(v)
         | 
| 60 | 
            +
                  }
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
                generic_response rows, properties.count
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
             | 
| 66 | 
            +
              def build_request keys, body, view_name = 'foo_bar'
         | 
| 67 | 
            +
                query = "keys=#{CGI::escape keys.to_s.gsub(' ','')}&" unless keys&.empty?
         | 
| 68 | 
            +
                stub_request(:get, "#{query_base_path}?#{query.to_s}include_docs=true").
         | 
| 69 | 
            +
                  to_return(body: body.to_json)
         | 
| 70 | 
            +
              end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
              def query_base_path
         | 
| 73 | 
            +
                "#{DB_BASE_PATH}/_all_docs"
         | 
| 74 | 
            +
              end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
              def build_save_request(obj)
         | 
| 77 | 
            +
                stub_request(:put, "#{DB_BASE_PATH}/#{CGI.escape(obj.id)}").
         | 
| 78 | 
            +
                  to_return(body: {ok: true, id: obj.id, rev: "FF0000" }.to_json)
         | 
| 79 | 
            +
              end
         | 
| 31 80 | 
             
            end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            class BaseDolly < Dolly::Document; end
         | 
| @@ -0,0 +1,27 @@ | |
| 1 | 
            +
             require 'test_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Foo < Dolly::Document
         | 
| 4 | 
            +
            end
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            class ViewQueryTest < Test::Unit::TestCase
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              def setup
         | 
| 9 | 
            +
                all_docs = [ {foo: 'Foo B', bar: 'Bar B', type: 'foo_bar'},  {foo: 'Foo A', bar: 'Bar A', type: 'foo_bar'}]
         | 
| 10 | 
            +
                @multi_type_resp = build_view_collation_response all_docs
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                stub_request(:get, "http://localhost:5984/test/_design/doc/_view/id?include_docs=true").
         | 
| 13 | 
            +
                  to_return(body: @multi_type_resp.to_json)
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              test 'raw_view' do
         | 
| 18 | 
            +
                assert_equal(Foo.raw_view('doc', 'id'), @multi_type_resp)
         | 
| 19 | 
            +
                assert_equal(Foo.raw_view('doc', 'id')[:rows].any?, true)
         | 
| 20 | 
            +
                assert_equal(Foo.raw_view('doc', 'id')[:total_rows].nil?, false)
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              test 'view_value' do
         | 
| 24 | 
            +
                expected = @multi_type_resp[:rows].flat_map{|res| res[:value]}
         | 
| 25 | 
            +
                assert_equal(Foo.view_value('doc', 'id'), expected)
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: dolly
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 3.0. | 
| 4 | 
            +
              version: 3.0.1
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - javierg
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: exe
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 11 | 
            +
            date: 2020-09-02 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: oj
         | 
| @@ -116,6 +116,8 @@ files: | |
| 116 116 | 
             
            - lib/dolly/document_type.rb
         | 
| 117 117 | 
             
            - lib/dolly/exceptions.rb
         | 
| 118 118 | 
             
            - lib/dolly/identity_properties.rb
         | 
| 119 | 
            +
            - lib/dolly/mango.rb
         | 
| 120 | 
            +
            - lib/dolly/mango_index.rb
         | 
| 119 121 | 
             
            - lib/dolly/properties.rb
         | 
| 120 122 | 
             
            - lib/dolly/property.rb
         | 
| 121 123 | 
             
            - lib/dolly/property_manager.rb
         | 
| @@ -126,15 +128,22 @@ files: | |
| 126 128 | 
             
            - lib/dolly/request_header.rb
         | 
| 127 129 | 
             
            - lib/dolly/timestamp.rb
         | 
| 128 130 | 
             
            - lib/dolly/version.rb
         | 
| 131 | 
            +
            - lib/dolly/view_query.rb
         | 
| 129 132 | 
             
            - lib/railties/railtie.rb
         | 
| 133 | 
            +
            - lib/refinements/hash_refinements.rb
         | 
| 130 134 | 
             
            - lib/refinements/string_refinements.rb
         | 
| 131 135 | 
             
            - lib/tasks/db.rake
         | 
| 132 136 | 
             
            - test/bulk_document_test.rb
         | 
| 133 137 | 
             
            - test/document_test.rb
         | 
| 138 | 
            +
            - test/document_type_test.rb
         | 
| 134 139 | 
             
            - test/dummy/config/initializers/filter_parameter_logging.rb
         | 
| 135 140 | 
             
            - test/dummy/log/test.log
         | 
| 141 | 
            +
            - test/inheritance_test.rb
         | 
| 142 | 
            +
            - test/mango_index_test.rb
         | 
| 143 | 
            +
            - test/mango_test.rb
         | 
| 136 144 | 
             
            - test/support/test.txt
         | 
| 137 145 | 
             
            - test/test_helper.rb
         | 
| 146 | 
            +
            - test/view_query_test.rb
         | 
| 138 147 | 
             
            homepage: https://www.amco.me
         | 
| 139 148 | 
             
            licenses: []
         | 
| 140 149 | 
             
            metadata: {}
         | 
| @@ -153,15 +162,19 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 153 162 | 
             
                - !ruby/object:Gem::Version
         | 
| 154 163 | 
             
                  version: '0'
         | 
| 155 164 | 
             
            requirements: []
         | 
| 156 | 
            -
             | 
| 157 | 
            -
            rubygems_version: 2.6.13
         | 
| 165 | 
            +
            rubygems_version: 3.1.4
         | 
| 158 166 | 
             
            signing_key: 
         | 
| 159 167 | 
             
            specification_version: 4
         | 
| 160 168 | 
             
            summary: will write something
         | 
| 161 169 | 
             
            test_files:
         | 
| 162 170 | 
             
            - test/dummy/config/initializers/filter_parameter_logging.rb
         | 
| 163 171 | 
             
            - test/dummy/log/test.log
         | 
| 172 | 
            +
            - test/inheritance_test.rb
         | 
| 173 | 
            +
            - test/document_type_test.rb
         | 
| 174 | 
            +
            - test/mango_index_test.rb
         | 
| 164 175 | 
             
            - test/bulk_document_test.rb
         | 
| 176 | 
            +
            - test/view_query_test.rb
         | 
| 177 | 
            +
            - test/mango_test.rb
         | 
| 165 178 | 
             
            - test/support/test.txt
         | 
| 166 179 | 
             
            - test/test_helper.rb
         | 
| 167 180 | 
             
            - test/document_test.rb
         |