guacamole 0.0.1 → 0.1.0
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 +4 -4
- data/.gitignore +1 -0
- data/{config/rubocop.yml → .hound.yml} +1 -12
- data/.ruby-version +1 -1
- data/.travis.yml +2 -0
- data/.yardopts +1 -0
- data/CONTRIBUTING.md +3 -3
- data/Gemfile.devtools +24 -12
- data/Guardfile +1 -1
- data/README.md +347 -50
- data/Rakefile +10 -0
- data/config/reek.yml +18 -5
- data/guacamole.gemspec +5 -2
- data/lib/guacamole.rb +1 -0
- data/lib/guacamole/collection.rb +79 -7
- data/lib/guacamole/configuration.rb +56 -2
- data/lib/guacamole/document_model_mapper.rb +87 -7
- data/lib/guacamole/identity_map.rb +124 -0
- data/lib/guacamole/proxies/proxy.rb +42 -0
- data/lib/guacamole/proxies/referenced_by.rb +15 -0
- data/lib/guacamole/proxies/references.rb +15 -0
- data/lib/guacamole/query.rb +11 -0
- data/lib/guacamole/railtie.rb +6 -1
- data/lib/guacamole/railtie/database.rake +57 -3
- data/lib/guacamole/tasks/database.rake +23 -0
- data/lib/guacamole/version.rb +1 -1
- data/lib/rails/generators/guacamole/collection/collection_generator.rb +19 -0
- data/lib/rails/generators/guacamole/collection/templates/collection.rb.tt +5 -0
- data/lib/rails/generators/guacamole/config/config_generator.rb +25 -0
- data/lib/rails/generators/guacamole/config/templates/guacamole.yml +15 -0
- data/lib/rails/generators/guacamole/model/model_generator.rb +25 -0
- data/lib/rails/generators/guacamole/model/templates/model.rb.tt +11 -0
- data/lib/rails/generators/guacamole_generator.rb +28 -0
- data/lib/rails/generators/rails/collection/collection_generator.rb +13 -0
- data/lib/rails/generators/rspec/collection/collection_generator.rb +13 -0
- data/lib/rails/generators/rspec/collection/templates/collection_spec.rb.tt +7 -0
- data/spec/acceptance/association_spec.rb +40 -0
- data/spec/acceptance/basic_spec.rb +19 -2
- data/spec/acceptance/spec_helper.rb +5 -2
- data/spec/fabricators/author.rb +11 -0
- data/spec/fabricators/author_fabricator.rb +7 -0
- data/spec/fabricators/book.rb +11 -0
- data/spec/fabricators/book_fabricator.rb +5 -0
- data/spec/unit/collection_spec.rb +265 -18
- data/spec/unit/configuration_spec.rb +11 -1
- data/spec/unit/document_model_mapper_spec.rb +127 -5
- data/spec/unit/identiy_map_spec.rb +140 -0
- data/spec/unit/query_spec.rb +37 -16
- data/tasks/adjustments.rake +0 -1
- metadata +78 -8
    
        data/Rakefile
    CHANGED
    
    | @@ -5,3 +5,13 @@ require 'devtools' | |
| 5 5 | 
             
            Devtools.init_rake_tasks
         | 
| 6 6 |  | 
| 7 7 | 
             
            import('./tasks/adjustments.rake')
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            desc 'Start a REPL with guacamole loaded (not the Rails part)'
         | 
| 10 | 
            +
            task :console do
         | 
| 11 | 
            +
              require 'bundler/setup'
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              require 'pry'
         | 
| 14 | 
            +
              require 'guacamole'
         | 
| 15 | 
            +
              ARGV.clear
         | 
| 16 | 
            +
              Pry.start
         | 
| 17 | 
            +
            end
         | 
    
        data/config/reek.yml
    CHANGED
    
    | @@ -4,7 +4,8 @@ Attribute: | |
| 4 4 | 
             
              exclude: []
         | 
| 5 5 | 
             
            BooleanParameter:
         | 
| 6 6 | 
             
              enabled: true
         | 
| 7 | 
            -
              exclude: | 
| 7 | 
            +
              exclude:
         | 
| 8 | 
            +
              - respond_to_missing?
         | 
| 8 9 | 
             
            ClassVariable:
         | 
| 9 10 | 
             
              enabled: true
         | 
| 10 11 | 
             
              exclude: []
         | 
| @@ -18,7 +19,10 @@ DataClump: | |
| 18 19 | 
             
              min_clump_size: 2
         | 
| 19 20 | 
             
            DuplicateMethodCall:
         | 
| 20 21 | 
             
              enabled: true
         | 
| 21 | 
            -
              exclude: | 
| 22 | 
            +
              exclude:
         | 
| 23 | 
            +
              - Guacamole::DocumentModelMapper#document_to_model
         | 
| 24 | 
            +
              - Guacamole::DocumentModelMapper#model_to_document
         | 
| 25 | 
            +
              - Guacamole::Configuration#_add_missing_methods_to_database
         | 
| 22 26 | 
             
              max_calls: 1
         | 
| 23 27 | 
             
              allow_calls: []
         | 
| 24 28 | 
             
            FeatureEnvy:
         | 
| @@ -26,7 +30,8 @@ FeatureEnvy: | |
| 26 30 | 
             
              exclude: []
         | 
| 27 31 | 
             
            IrresponsibleModule:
         | 
| 28 32 | 
             
              enabled: true
         | 
| 29 | 
            -
              exclude: | 
| 33 | 
            +
              exclude:
         | 
| 34 | 
            +
              - - !ruby/regexp /Generators/
         | 
| 30 35 | 
             
            LongParameterList:
         | 
| 31 36 | 
             
              enabled: true
         | 
| 32 37 | 
             
              exclude: []
         | 
| @@ -40,7 +45,8 @@ LongYieldList: | |
| 40 45 | 
             
              max_params: 2
         | 
| 41 46 | 
             
            NestedIterators:
         | 
| 42 47 | 
             
              enabled: true
         | 
| 43 | 
            -
              exclude: | 
| 48 | 
            +
              exclude:
         | 
| 49 | 
            +
              - Guacamole::Configuration#_add_missing_methods_to_database
         | 
| 44 50 | 
             
              max_allowed_nesting: 2
         | 
| 45 51 | 
             
              ignore_iterators: []
         | 
| 46 52 | 
             
            NilCheck:
         | 
| @@ -52,7 +58,8 @@ RepeatedConditional: | |
| 52 58 | 
             
              max_ifs: 2
         | 
| 53 59 | 
             
            TooManyInstanceVariables:
         | 
| 54 60 | 
             
              enabled: true
         | 
| 55 | 
            -
              exclude: | 
| 61 | 
            +
              exclude:
         | 
| 62 | 
            +
              - Guacamole::DocumentModelMapper
         | 
| 56 63 | 
             
              max_instance_variables: 3
         | 
| 57 64 | 
             
            TooManyMethods:
         | 
| 58 65 | 
             
              enabled: true
         | 
| @@ -62,6 +69,12 @@ TooManyStatements: | |
| 62 69 | 
             
              enabled: true
         | 
| 63 70 | 
             
              exclude:
         | 
| 64 71 | 
             
              - each
         | 
| 72 | 
            +
              - Guacamole::DocumentModelMapper#document_to_model
         | 
| 73 | 
            +
              - Guacamole::DocumentModelMapper#model_to_document
         | 
| 74 | 
            +
              - Guacamole::Collection::ClassMethods#create_document_from
         | 
| 75 | 
            +
              - Guacamole::Collection::ClassMethods#create_referenced_by_models_of
         | 
| 76 | 
            +
              - Guacamole::Configuration#_add_missing_methods_to_database
         | 
| 77 | 
            +
              - Guacamole::Configuration#create_database_connection_from
         | 
| 65 78 | 
             
              max_statements: 5
         | 
| 66 79 | 
             
            UncommunicativeMethodName:
         | 
| 67 80 | 
             
              enabled: true
         | 
    
        data/guacamole.gemspec
    CHANGED
    
    | @@ -18,11 +18,14 @@ Gem::Specification.new do |spec| | |
| 18 18 | 
             
              spec.test_files    = spec.files.grep(%r{^(spec)/})
         | 
| 19 19 | 
             
              spec.require_paths = ['lib']
         | 
| 20 20 |  | 
| 21 | 
            -
              spec.add_dependency 'ashikawa-core', '~> 0. | 
| 22 | 
            -
              spec.add_dependency 'virtus', '~> 1.0. | 
| 21 | 
            +
              spec.add_dependency 'ashikawa-core', '~> 0.10.0'
         | 
| 22 | 
            +
              spec.add_dependency 'virtus', '~> 1.0.1'
         | 
| 23 23 | 
             
              spec.add_dependency 'activesupport', '>= 4.0.0'
         | 
| 24 24 | 
             
              spec.add_dependency 'activemodel', '>= 4.0.0'
         | 
| 25 | 
            +
              spec.add_dependency 'hamster', '~> 1.0.1.pre.rc.1'
         | 
| 25 26 |  | 
| 26 27 | 
             
              spec.add_development_dependency 'fabrication', '~> 2.8.1'
         | 
| 28 | 
            +
              spec.add_development_dependency 'faker', '~> 1.2.0'
         | 
| 27 29 | 
             
              spec.add_development_dependency 'logging', '~> 1.8.1'
         | 
| 30 | 
            +
              spec.add_development_dependency 'pry', '~> 0.9.12'
         | 
| 28 31 | 
             
            end
         | 
    
        data/lib/guacamole.rb
    CHANGED
    
    
    
        data/lib/guacamole/collection.rb
    CHANGED
    
    | @@ -16,7 +16,6 @@ module Guacamole | |
| 16 16 | 
             
              # the collection. See the `ClassMethods` submodule for details
         | 
| 17 17 | 
             
              module Collection
         | 
| 18 18 | 
             
                extend ActiveSupport::Concern
         | 
| 19 | 
            -
             | 
| 20 19 | 
             
                # The class methods added to the class via the mixin
         | 
| 21 20 | 
             
                #
         | 
| 22 21 | 
             
                # @!method model_to_document(model)
         | 
| @@ -51,6 +50,9 @@ module Guacamole | |
| 51 50 | 
             
                  # You can use this method for low level communication with the collection.
         | 
| 52 51 | 
             
                  # Details can be found in the Ashikawa::Core documentation.
         | 
| 53 52 | 
             
                  #
         | 
| 53 | 
            +
                  # @note We're well aware that we return a Ashikawa::Core::Collection here
         | 
| 54 | 
            +
                  #       but naming it a connection. We think the name `connection` still
         | 
| 55 | 
            +
                  #       fits better in this context.
         | 
| 54 56 | 
             
                  # @see http://rubydoc.info/gems/ashikawa-core/Ashikawa/Core/Collection
         | 
| 55 57 | 
             
                  # @return [Ashikawa::Core::Collection]
         | 
| 56 58 | 
             
                  def connection
         | 
| @@ -96,6 +98,31 @@ module Guacamole | |
| 96 98 | 
             
                    mapper.document_to_model connection.fetch(key)
         | 
| 97 99 | 
             
                  end
         | 
| 98 100 |  | 
| 101 | 
            +
                  # Persist a model in the collection or replace it in the database, depending if it is already persisted
         | 
| 102 | 
            +
                  #
         | 
| 103 | 
            +
                  # * If {Model#persisted? model#persisted?} is `false`, the model will be saved in the collection.
         | 
| 104 | 
            +
                  #   Timestamps, revision and key will be set on the model.
         | 
| 105 | 
            +
                  # * If {Model#persisted? model#persisted?} is `true`, it replaces the currently saved version of the model with
         | 
| 106 | 
            +
                  #   its new version. It searches for the entry in the database
         | 
| 107 | 
            +
                  #   by key. This will change the updated_at timestamp and revision
         | 
| 108 | 
            +
                  #   of the provided model.
         | 
| 109 | 
            +
                  #
         | 
| 110 | 
            +
                  # See also {#create create} and {#replace replace} for explicit usage.
         | 
| 111 | 
            +
                  #
         | 
| 112 | 
            +
                  # @param [Model] model The model to be saved
         | 
| 113 | 
            +
                  # @return [Model] The provided model
         | 
| 114 | 
            +
                  # @example Save a podcast to the database
         | 
| 115 | 
            +
                  #   podcast = Podcast.new(title: 'Best Show', guest: 'Dirk Breuer')
         | 
| 116 | 
            +
                  #   PodcastsCollection.save(podcast)
         | 
| 117 | 
            +
                  #   podcast.key #=> '27214247'
         | 
| 118 | 
            +
                  # @example Get a podcast, update its title, replace it
         | 
| 119 | 
            +
                  #   podcast = PodcastsCollection.by_key('27214247')
         | 
| 120 | 
            +
                  #   podcast.title = 'Even better'
         | 
| 121 | 
            +
                  #   PodcastsCollection.save(podcast)
         | 
| 122 | 
            +
                  def save(model)
         | 
| 123 | 
            +
                    model.persisted? ? replace(model) : create(model)
         | 
| 124 | 
            +
                  end
         | 
| 125 | 
            +
             | 
| 99 126 | 
             
                  # Persist a model in the collection
         | 
| 100 127 | 
             
                  #
         | 
| 101 128 | 
             
                  # The model will be saved in the collection. Timestamps, revision
         | 
| @@ -107,7 +134,7 @@ module Guacamole | |
| 107 134 | 
             
                  #   podcast = Podcast.new(title: 'Best Show', guest: 'Dirk Breuer')
         | 
| 108 135 | 
             
                  #   PodcastsCollection.save(podcast)
         | 
| 109 136 | 
             
                  #   podcast.key #=> '27214247'
         | 
| 110 | 
            -
                  def  | 
| 137 | 
            +
                  def create(model)
         | 
| 111 138 | 
             
                    return false unless model.valid?
         | 
| 112 139 |  | 
| 113 140 | 
             
                    add_timestamps_to_model(model)
         | 
| @@ -125,10 +152,10 @@ module Guacamole | |
| 125 152 | 
             
                  #   PodcastsCollection.delete(podcast)
         | 
| 126 153 | 
             
                  def delete(model_or_key)
         | 
| 127 154 | 
             
                    key = if model_or_key.respond_to? :key
         | 
| 128 | 
            -
             | 
| 129 | 
            -
             | 
| 130 | 
            -
             | 
| 131 | 
            -
             | 
| 155 | 
            +
                            model_or_key.key
         | 
| 156 | 
            +
                          else
         | 
| 157 | 
            +
                            model_or_key
         | 
| 158 | 
            +
                          end
         | 
| 132 159 | 
             
                    fetch_document(key).delete
         | 
| 133 160 | 
             
                    key
         | 
| 134 161 | 
             
                  end
         | 
| @@ -220,18 +247,64 @@ module Guacamole | |
| 220 247 | 
             
                  # Create a document from a model
         | 
| 221 248 | 
             
                  #
         | 
| 222 249 | 
             
                  # @api private
         | 
| 250 | 
            +
                  # @todo Currently we only save the associated models if those never have been
         | 
| 251 | 
            +
                  #       persisted. In future versions we should add something like `:autosave`
         | 
| 252 | 
            +
                  #       to always save associated models.
         | 
| 223 253 | 
             
                  def create_document_from(model)
         | 
| 254 | 
            +
                    create_referenced_models_of model
         | 
| 255 | 
            +
             | 
| 224 256 | 
             
                    document = connection.create_document(model_to_document(model))
         | 
| 225 257 |  | 
| 226 258 | 
             
                    model.key = document.key
         | 
| 227 259 | 
             
                    model.rev = document.revision
         | 
| 228 260 |  | 
| 261 | 
            +
                    create_referenced_by_models_of model
         | 
| 262 | 
            +
             | 
| 229 263 | 
             
                    document
         | 
| 230 264 | 
             
                  end
         | 
| 231 265 |  | 
| 266 | 
            +
                  # Creates all not yet persisted referenced models of `model`
         | 
| 267 | 
            +
                  #
         | 
| 268 | 
            +
                  # Referenced models needs to be created before the parent model, because it needs their `key`
         | 
| 269 | 
            +
                  #
         | 
| 270 | 
            +
                  # @api private
         | 
| 271 | 
            +
                  # @todo This method should be considered 'work in progress'. We already know we need to change this.
         | 
| 272 | 
            +
                  # @return [void]
         | 
| 273 | 
            +
                  def create_referenced_models_of(model)
         | 
| 274 | 
            +
                    mapper.referenced_models.each do |ref_model_name|
         | 
| 275 | 
            +
                      ref_collection = mapper.collection_for(ref_model_name)
         | 
| 276 | 
            +
             | 
| 277 | 
            +
                      ref_model = model.send(ref_model_name)
         | 
| 278 | 
            +
                      next unless ref_model
         | 
| 279 | 
            +
             | 
| 280 | 
            +
                      ref_collection.save ref_model unless ref_model.persisted?
         | 
| 281 | 
            +
                    end
         | 
| 282 | 
            +
                  end
         | 
| 283 | 
            +
             | 
| 284 | 
            +
                  # Creates all not yet persisted models which are referenced by `model`
         | 
| 285 | 
            +
                  #
         | 
| 286 | 
            +
                  # Referenced by models needs to created after the parent model, because they need its `key`
         | 
| 287 | 
            +
                  #
         | 
| 288 | 
            +
                  # @api private
         | 
| 289 | 
            +
                  # @todo This method should be considered 'work in progress'. We already know we need to change this.
         | 
| 290 | 
            +
                  # @return [void]
         | 
| 291 | 
            +
                  def create_referenced_by_models_of(model)
         | 
| 292 | 
            +
                    mapper.referenced_by_models.each do |ref_model_name|
         | 
| 293 | 
            +
                      ref_collection = mapper.collection_for(ref_model_name)
         | 
| 294 | 
            +
             | 
| 295 | 
            +
                      ref_models = model.send(ref_model_name)
         | 
| 296 | 
            +
             | 
| 297 | 
            +
                      ref_models.each do |ref_model|
         | 
| 298 | 
            +
                        ref_model.send("#{model.class.name.demodulize.underscore}=", model)
         | 
| 299 | 
            +
                        ref_collection.save ref_model unless ref_model.persisted?
         | 
| 300 | 
            +
                      end
         | 
| 301 | 
            +
                    end
         | 
| 302 | 
            +
                  end
         | 
| 303 | 
            +
             | 
| 232 304 | 
             
                  # Replace a document in the database with this model
         | 
| 233 305 | 
             
                  #
         | 
| 234 306 | 
             
                  # @api private
         | 
| 307 | 
            +
                  # @note This will **not** update associated models (see {#create})
         | 
| 235 308 | 
             
                  def replace_document_from(model)
         | 
| 236 309 | 
             
                    document = model_to_document(model)
         | 
| 237 310 | 
             
                    response = connection.replace(model.key, document)
         | 
| @@ -240,7 +313,6 @@ module Guacamole | |
| 240 313 |  | 
| 241 314 | 
             
                    document
         | 
| 242 315 | 
             
                  end
         | 
| 243 | 
            -
             | 
| 244 316 | 
             
                end
         | 
| 245 317 | 
             
              end
         | 
| 246 318 | 
             
            end
         | 
| @@ -4,11 +4,11 @@ require 'logger' | |
| 4 4 | 
             
            require 'forwardable'
         | 
| 5 5 | 
             
            require 'ashikawa-core'
         | 
| 6 6 | 
             
            require 'active_support/core_ext'
         | 
| 7 | 
            +
            require 'yaml'
         | 
| 7 8 |  | 
| 8 9 | 
             
            require 'guacamole/document_model_mapper'
         | 
| 9 10 |  | 
| 10 11 | 
             
            module Guacamole
         | 
| 11 | 
            -
             | 
| 12 12 | 
             
              class << self
         | 
| 13 13 | 
             
                # Configure Guacamole
         | 
| 14 14 | 
             
                #
         | 
| @@ -26,6 +26,13 @@ module Guacamole | |
| 26 26 | 
             
                def configuration
         | 
| 27 27 | 
             
                  @configuration ||= Configuration
         | 
| 28 28 | 
             
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                # Just an alias to Configuration#logger
         | 
| 31 | 
            +
                #
         | 
| 32 | 
            +
                # @return [Configuration#logger]
         | 
| 33 | 
            +
                def logger
         | 
| 34 | 
            +
                  configuration.logger
         | 
| 35 | 
            +
                end
         | 
| 29 36 | 
             
              end
         | 
| 30 37 |  | 
| 31 38 | 
             
              # Current configuration
         | 
| @@ -97,12 +104,16 @@ module Guacamole | |
| 97 104 | 
             
                  end
         | 
| 98 105 |  | 
| 99 106 | 
             
                  def create_database_connection_from(config)
         | 
| 100 | 
            -
                    Ashikawa::Core::Database.new do |arango_config|
         | 
| 107 | 
            +
                    database = Ashikawa::Core::Database.new do |arango_config|
         | 
| 101 108 | 
             
                      arango_config.url      = db_url_from(config)
         | 
| 102 109 | 
             
                      arango_config.username = config['username']
         | 
| 103 110 | 
             
                      arango_config.password = config['password']
         | 
| 104 111 | 
             
                      arango_config.logger   = logger
         | 
| 105 112 | 
             
                    end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                    _add_missing_methods_to_database(database)
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                    database
         | 
| 106 117 | 
             
                  end
         | 
| 107 118 |  | 
| 108 119 | 
             
                  def db_url_from(config)
         | 
| @@ -118,6 +129,49 @@ module Guacamole | |
| 118 129 | 
             
                    default_logger.level = Logger::INFO
         | 
| 119 130 | 
             
                    default_logger
         | 
| 120 131 | 
             
                  end
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                  # FIXME: This is not here to stay! Kill it with fire!
         | 
| 134 | 
            +
                  #
         | 
| 135 | 
            +
                  # As soon Ashikawa::Core provides those features
         | 
| 136 | 
            +
                  # (https://github.com/triAGENS/ashikawa-core/issues/83) immediately
         | 
| 137 | 
            +
                  # remove this hack. But while this is ugly as hell it ensures we don't
         | 
| 138 | 
            +
                  # need to change any other related code. Just remove this and we're good.
         | 
| 139 | 
            +
                  def _add_missing_methods_to_database(database)
         | 
| 140 | 
            +
                    database.singleton_class.instance_eval do
         | 
| 141 | 
            +
                      # The raw Faraday connection
         | 
| 142 | 
            +
                      define_method(:raw_connection) do
         | 
| 143 | 
            +
                        @connection.connection
         | 
| 144 | 
            +
                      end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                      # The base URI to the ArangoDB server
         | 
| 147 | 
            +
                      define_method(:arangodb_uri) do |additional_path = ''|
         | 
| 148 | 
            +
                        uri = raw_connection.url_prefix
         | 
| 149 | 
            +
                        base_uri = [uri.scheme, '://', uri.host, ':', uri.port].join
         | 
| 150 | 
            +
                        URI.join(base_uri, additional_path)
         | 
| 151 | 
            +
                      end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                      # Database name query method
         | 
| 154 | 
            +
                      define_method(:name) do
         | 
| 155 | 
            +
                        database_regexp = %r{_db/(?<db_name>\w+)/_api}
         | 
| 156 | 
            +
                        raw_connection.url_prefix.to_s.match(database_regexp)['db_name']
         | 
| 157 | 
            +
                      end
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                      # Creates the database
         | 
| 160 | 
            +
                      define_method(:create) do
         | 
| 161 | 
            +
                        raw_connection.post(arangodb_uri('/_api/database'), name: name)
         | 
| 162 | 
            +
                      end
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                      # Drops the database
         | 
| 165 | 
            +
                      define_method(:drop) do
         | 
| 166 | 
            +
                        raw_connection.delete(arangodb_uri("/_api/database/#{name}"))
         | 
| 167 | 
            +
                      end
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                      # Truncate the database
         | 
| 170 | 
            +
                      define_method(:truncate) do
         | 
| 171 | 
            +
                        collections.each { |collection| collection.truncate! }
         | 
| 172 | 
            +
                      end
         | 
| 173 | 
            +
                    end
         | 
| 174 | 
            +
                  end
         | 
| 121 175 | 
             
                end
         | 
| 122 176 | 
             
              end
         | 
| 123 177 | 
             
            end
         | 
| @@ -1,11 +1,16 @@ | |
| 1 1 | 
             
            # -*- encoding : utf-8 -*-
         | 
| 2 2 |  | 
| 3 | 
            +
            require 'guacamole/proxies/referenced_by'
         | 
| 4 | 
            +
            require 'guacamole/proxies/references'
         | 
| 5 | 
            +
             | 
| 3 6 | 
             
            module Guacamole
         | 
| 4 7 | 
             
              # This is the default mapper class to map between Ashikawa::Core::Document and
         | 
| 5 8 | 
             
              # Guacamole::Model instances.
         | 
| 6 9 | 
             
              #
         | 
| 7 10 | 
             
              # If you want to build your own mapper, you have to build at least the
         | 
| 8 11 | 
             
              # `document_to_model` and `model_to_document` methods.
         | 
| 12 | 
            +
              #
         | 
| 13 | 
            +
              # @note If you plan to bring your own `DocumentModelMapper` please consider using an {Guacamole::IdentityMap}.
         | 
| 9 14 | 
             
              class DocumentModelMapper
         | 
| 10 15 | 
             
                # The class to map to
         | 
| 11 16 | 
             
                #
         | 
| @@ -16,6 +21,8 @@ module Guacamole | |
| 16 21 | 
             
                #
         | 
| 17 22 | 
             
                # @return [Array] An array of embedded models
         | 
| 18 23 | 
             
                attr_reader :models_to_embed
         | 
| 24 | 
            +
                attr_reader :referenced_by_models
         | 
| 25 | 
            +
                attr_reader :referenced_models
         | 
| 19 26 |  | 
| 20 27 | 
             
                # Create a new instance of the mapper
         | 
| 21 28 | 
             
                #
         | 
| @@ -23,9 +30,42 @@ module Guacamole | |
| 23 30 | 
             
                # The Document class is always Ashikawa::Core::Document
         | 
| 24 31 | 
             
                #
         | 
| 25 32 | 
             
                # @param [Class] model_class
         | 
| 26 | 
            -
                def initialize(model_class)
         | 
| 27 | 
            -
                  @model_class | 
| 28 | 
            -
                  @ | 
| 33 | 
            +
                def initialize(model_class, identity_map = IdentityMap)
         | 
| 34 | 
            +
                  @model_class          = model_class
         | 
| 35 | 
            +
                  @identity_map         = identity_map
         | 
| 36 | 
            +
                  @models_to_embed      = []
         | 
| 37 | 
            +
                  @referenced_by_models = []
         | 
| 38 | 
            +
                  @referenced_models    = []
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                class << self
         | 
| 42 | 
            +
                  # construct the {collection} class for a given model name.
         | 
| 43 | 
            +
                  #
         | 
| 44 | 
            +
                  # @example
         | 
| 45 | 
            +
                  #   collection_class = collection_for(:user)
         | 
| 46 | 
            +
                  #   collection_class == userscollection # would be true
         | 
| 47 | 
            +
                  #
         | 
| 48 | 
            +
                  # @note This is an class level alias for {DocumentModelMapper#collection_for}
         | 
| 49 | 
            +
                  # @param [symbol, string] model_name the name of the model
         | 
| 50 | 
            +
                  # @return [class] the {collection} class for the given model name
         | 
| 51 | 
            +
                  def collection_for(model_name)
         | 
| 52 | 
            +
                    "#{model_name.to_s.classify.pluralize}Collection".constantize
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                # construct the {collection} class for a given model name.
         | 
| 57 | 
            +
                #
         | 
| 58 | 
            +
                # @example
         | 
| 59 | 
            +
                #   collection_class = collection_for(:user)
         | 
| 60 | 
            +
                #   collection_class == userscollection # would be true
         | 
| 61 | 
            +
                #
         | 
| 62 | 
            +
                # @todo As of now this is some kind of placeholder method. As soon as we implement
         | 
| 63 | 
            +
                #       the configuration of the mapping (#12) this will change. Still the {DocumentModelMapper}
         | 
| 64 | 
            +
                #       seems to be a good place for this functionality.
         | 
| 65 | 
            +
                # @param [symbol, string] model_name the name of the model
         | 
| 66 | 
            +
                # @return [class] the {collection} class for the given model name
         | 
| 67 | 
            +
                def collection_for(model_name)
         | 
| 68 | 
            +
                  self.class.collection_for model_name
         | 
| 29 69 | 
             
                end
         | 
| 30 70 |  | 
| 31 71 | 
             
                # Map a document to a model
         | 
| @@ -34,14 +74,26 @@ module Guacamole | |
| 34 74 | 
             
                #
         | 
| 35 75 | 
             
                # @param [Ashikawa::Core::Document] document
         | 
| 36 76 | 
             
                # @return [Model] the resulting model with the given Model class
         | 
| 77 | 
            +
                # rubocop:disable MethodLength
         | 
| 37 78 | 
             
                def document_to_model(document)
         | 
| 38 | 
            -
                   | 
| 79 | 
            +
                  identity_map.retrieve_or_store model_class, document.key do
         | 
| 80 | 
            +
                    model = model_class.new(document.to_h)
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                    referenced_by_models.each do |ref_model_name|
         | 
| 83 | 
            +
                      model.send("#{ref_model_name}=", Proxies::ReferencedBy.new(ref_model_name, model))
         | 
| 84 | 
            +
                    end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                    referenced_models.each do |ref_model_name|
         | 
| 87 | 
            +
                      model.send("#{ref_model_name}=", Proxies::References.new(ref_model_name, document))
         | 
| 88 | 
            +
                    end
         | 
| 39 89 |  | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 90 | 
            +
                    model.key = document.key
         | 
| 91 | 
            +
                    model.rev = document.revision
         | 
| 42 92 |  | 
| 43 | 
            -
             | 
| 93 | 
            +
                    model
         | 
| 94 | 
            +
                  end
         | 
| 44 95 | 
             
                end
         | 
| 96 | 
            +
                # rubocop:enable MethodLength
         | 
| 45 97 |  | 
| 46 98 | 
             
                # Map a model to a document
         | 
| 47 99 | 
             
                #
         | 
| @@ -49,6 +101,7 @@ module Guacamole | |
| 49 101 | 
             
                #
         | 
| 50 102 | 
             
                # @param [Model] model
         | 
| 51 103 | 
             
                # @return [Ashikawa::Core::Document] the resulting document
         | 
| 104 | 
            +
                # rubocop:disable MethodLength
         | 
| 52 105 | 
             
                def model_to_document(model)
         | 
| 53 106 | 
             
                  document = model.attributes.dup.except(:key, :rev)
         | 
| 54 107 | 
             
                  models_to_embed.each do |attribute_name|
         | 
| @@ -56,8 +109,21 @@ module Guacamole | |
| 56 109 | 
             
                      embedded_model.attributes.except(:key, :rev)
         | 
| 57 110 | 
             
                    end
         | 
| 58 111 | 
             
                  end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                  referenced_models.each do |ref_model_name|
         | 
| 114 | 
            +
                    ref_key = [ref_model_name.to_s, 'id'].join('_').to_sym
         | 
| 115 | 
            +
                    ref_model = model.send ref_model_name
         | 
| 116 | 
            +
                    document[ref_key] = ref_model.key if ref_model
         | 
| 117 | 
            +
                    document.delete(ref_model_name)
         | 
| 118 | 
            +
                  end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                  referenced_by_models.each do |ref_model_name|
         | 
| 121 | 
            +
                    document.delete(ref_model_name)
         | 
| 122 | 
            +
                  end
         | 
| 123 | 
            +
             | 
| 59 124 | 
             
                  document
         | 
| 60 125 | 
             
                end
         | 
| 126 | 
            +
                # rubocop:enable MethodLength
         | 
| 61 127 |  | 
| 62 128 | 
             
                # Declare a model to be embedded
         | 
| 63 129 | 
             
                #
         | 
| @@ -91,5 +157,19 @@ module Guacamole | |
| 91 157 | 
             
                def embeds(model_name)
         | 
| 92 158 | 
             
                  @models_to_embed << model_name
         | 
| 93 159 | 
             
                end
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                def referenced_by(model_name)
         | 
| 162 | 
            +
                  @referenced_by_models << model_name
         | 
| 163 | 
            +
                end
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                def references(model_name)
         | 
| 166 | 
            +
                  @referenced_models << model_name
         | 
| 167 | 
            +
                end
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                private
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                def identity_map
         | 
| 172 | 
            +
                  @identity_map
         | 
| 173 | 
            +
                end
         | 
| 94 174 | 
             
              end
         | 
| 95 175 | 
             
            end
         |