hanami-model 0.0.0 → 0.6.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/CHANGELOG.md +145 -0
 - data/EXAMPLE.md +212 -0
 - data/LICENSE.md +22 -0
 - data/README.md +600 -7
 - data/hanami-model.gemspec +17 -12
 - data/lib/hanami-model.rb +1 -0
 - data/lib/hanami/entity.rb +298 -0
 - data/lib/hanami/entity/dirty_tracking.rb +74 -0
 - data/lib/hanami/model.rb +204 -2
 - data/lib/hanami/model/adapters/abstract.rb +281 -0
 - data/lib/hanami/model/adapters/file_system_adapter.rb +288 -0
 - data/lib/hanami/model/adapters/implementation.rb +111 -0
 - data/lib/hanami/model/adapters/memory/collection.rb +132 -0
 - data/lib/hanami/model/adapters/memory/command.rb +113 -0
 - data/lib/hanami/model/adapters/memory/query.rb +653 -0
 - data/lib/hanami/model/adapters/memory_adapter.rb +179 -0
 - data/lib/hanami/model/adapters/null_adapter.rb +24 -0
 - data/lib/hanami/model/adapters/sql/collection.rb +287 -0
 - data/lib/hanami/model/adapters/sql/command.rb +73 -0
 - data/lib/hanami/model/adapters/sql/console.rb +33 -0
 - data/lib/hanami/model/adapters/sql/consoles/mysql.rb +49 -0
 - data/lib/hanami/model/adapters/sql/consoles/postgresql.rb +48 -0
 - data/lib/hanami/model/adapters/sql/consoles/sqlite.rb +26 -0
 - data/lib/hanami/model/adapters/sql/query.rb +788 -0
 - data/lib/hanami/model/adapters/sql_adapter.rb +296 -0
 - data/lib/hanami/model/coercer.rb +74 -0
 - data/lib/hanami/model/config/adapter.rb +116 -0
 - data/lib/hanami/model/config/mapper.rb +45 -0
 - data/lib/hanami/model/configuration.rb +275 -0
 - data/lib/hanami/model/error.rb +7 -0
 - data/lib/hanami/model/mapper.rb +124 -0
 - data/lib/hanami/model/mapping.rb +48 -0
 - data/lib/hanami/model/mapping/attribute.rb +85 -0
 - data/lib/hanami/model/mapping/coercers.rb +314 -0
 - data/lib/hanami/model/mapping/collection.rb +490 -0
 - data/lib/hanami/model/mapping/collection_coercer.rb +79 -0
 - data/lib/hanami/model/migrator.rb +324 -0
 - data/lib/hanami/model/migrator/adapter.rb +170 -0
 - data/lib/hanami/model/migrator/connection.rb +133 -0
 - data/lib/hanami/model/migrator/mysql_adapter.rb +72 -0
 - data/lib/hanami/model/migrator/postgres_adapter.rb +119 -0
 - data/lib/hanami/model/migrator/sqlite_adapter.rb +110 -0
 - data/lib/hanami/model/version.rb +4 -1
 - data/lib/hanami/repository.rb +872 -0
 - metadata +100 -16
 - data/.gitignore +0 -9
 - data/Gemfile +0 -4
 - data/Rakefile +0 -2
 - data/bin/console +0 -14
 - data/bin/setup +0 -8
 
| 
         @@ -0,0 +1,296 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'hanami/model/adapters/abstract'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'hanami/model/adapters/implementation'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'hanami/model/adapters/sql/collection'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'hanami/model/adapters/sql/command'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'hanami/model/adapters/sql/query'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'hanami/model/adapters/sql/console'
         
     | 
| 
      
 7 
     | 
    
         
            +
            require 'sequel'
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            module Hanami
         
     | 
| 
      
 10 
     | 
    
         
            +
              module Model
         
     | 
| 
      
 11 
     | 
    
         
            +
                module Adapters
         
     | 
| 
      
 12 
     | 
    
         
            +
                  # Adapter for SQL databases
         
     | 
| 
      
 13 
     | 
    
         
            +
                  #
         
     | 
| 
      
 14 
     | 
    
         
            +
                  # In order to use it with a specific database, you must require the Ruby
         
     | 
| 
      
 15 
     | 
    
         
            +
                  # gem before of loading Hanami::Model.
         
     | 
| 
      
 16 
     | 
    
         
            +
                  #
         
     | 
| 
      
 17 
     | 
    
         
            +
                  # @see Hanami::Model::Adapters::Implementation
         
     | 
| 
      
 18 
     | 
    
         
            +
                  #
         
     | 
| 
      
 19 
     | 
    
         
            +
                  # @api private
         
     | 
| 
      
 20 
     | 
    
         
            +
                  # @since 0.1.0
         
     | 
| 
      
 21 
     | 
    
         
            +
                  class SqlAdapter < Abstract
         
     | 
| 
      
 22 
     | 
    
         
            +
                    include Implementation
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                    # Initialize the adapter.
         
     | 
| 
      
 25 
     | 
    
         
            +
                    #
         
     | 
| 
      
 26 
     | 
    
         
            +
                    # Hanami::Model uses Sequel. For a complete reference of the connection
         
     | 
| 
      
 27 
     | 
    
         
            +
                    # URI, please see: http://sequel.jeremyevans.net/rdoc/files/doc/opening_databases_rdoc.html
         
     | 
| 
      
 28 
     | 
    
         
            +
                    #
         
     | 
| 
      
 29 
     | 
    
         
            +
                    # @param mapper [Object] the database mapper
         
     | 
| 
      
 30 
     | 
    
         
            +
                    # @param uri [String] the connection uri for the database
         
     | 
| 
      
 31 
     | 
    
         
            +
                    # @param options [Hash] a hash of non-mandatory adapter options
         
     | 
| 
      
 32 
     | 
    
         
            +
                    #
         
     | 
| 
      
 33 
     | 
    
         
            +
                    # @return [Hanami::Model::Adapters::SqlAdapter]
         
     | 
| 
      
 34 
     | 
    
         
            +
                    #
         
     | 
| 
      
 35 
     | 
    
         
            +
                    # @raise [Hanami::Model::Adapters::DatabaseAdapterNotFound] if the given
         
     | 
| 
      
 36 
     | 
    
         
            +
                    #   URI refers to an unknown or not registered adapter.
         
     | 
| 
      
 37 
     | 
    
         
            +
                    #
         
     | 
| 
      
 38 
     | 
    
         
            +
                    # @raise [URI::InvalidURIError] if the given URI is malformed
         
     | 
| 
      
 39 
     | 
    
         
            +
                    #
         
     | 
| 
      
 40 
     | 
    
         
            +
                    # @see Hanami::Model::Mapper
         
     | 
| 
      
 41 
     | 
    
         
            +
                    # @see http://sequel.jeremyevans.net/rdoc/files/doc/opening_databases_rdoc.html
         
     | 
| 
      
 42 
     | 
    
         
            +
                    #
         
     | 
| 
      
 43 
     | 
    
         
            +
                    # @api private
         
     | 
| 
      
 44 
     | 
    
         
            +
                    # @since 0.1.0
         
     | 
| 
      
 45 
     | 
    
         
            +
                    def initialize(mapper, uri, options = {})
         
     | 
| 
      
 46 
     | 
    
         
            +
                      super
         
     | 
| 
      
 47 
     | 
    
         
            +
                      @connection = Sequel.connect(@uri, @options)
         
     | 
| 
      
 48 
     | 
    
         
            +
                    rescue Sequel::AdapterNotFound => e
         
     | 
| 
      
 49 
     | 
    
         
            +
                      raise DatabaseAdapterNotFound.new(e.message)
         
     | 
| 
      
 50 
     | 
    
         
            +
                    end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                    # Creates a record in the database for the given entity.
         
     | 
| 
      
 53 
     | 
    
         
            +
                    # It assigns the `id` attribute, in case of success.
         
     | 
| 
      
 54 
     | 
    
         
            +
                    #
         
     | 
| 
      
 55 
     | 
    
         
            +
                    # @param collection [Symbol] the target collection (it must be mapped).
         
     | 
| 
      
 56 
     | 
    
         
            +
                    # @param entity [#id=] the entity to create
         
     | 
| 
      
 57 
     | 
    
         
            +
                    #
         
     | 
| 
      
 58 
     | 
    
         
            +
                    # @return [Object] the entity
         
     | 
| 
      
 59 
     | 
    
         
            +
                    #
         
     | 
| 
      
 60 
     | 
    
         
            +
                    # @api private
         
     | 
| 
      
 61 
     | 
    
         
            +
                    # @since 0.1.0
         
     | 
| 
      
 62 
     | 
    
         
            +
                    def create(collection, entity)
         
     | 
| 
      
 63 
     | 
    
         
            +
                      command(
         
     | 
| 
      
 64 
     | 
    
         
            +
                        query(collection)
         
     | 
| 
      
 65 
     | 
    
         
            +
                      ).create(entity)
         
     | 
| 
      
 66 
     | 
    
         
            +
                    end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                    # Updates a record in the database corresponding to the given entity.
         
     | 
| 
      
 69 
     | 
    
         
            +
                    #
         
     | 
| 
      
 70 
     | 
    
         
            +
                    # @param collection [Symbol] the target collection (it must be mapped).
         
     | 
| 
      
 71 
     | 
    
         
            +
                    # @param entity [#id] the entity to update
         
     | 
| 
      
 72 
     | 
    
         
            +
                    #
         
     | 
| 
      
 73 
     | 
    
         
            +
                    # @return [Object] the entity
         
     | 
| 
      
 74 
     | 
    
         
            +
                    #
         
     | 
| 
      
 75 
     | 
    
         
            +
                    # @api private
         
     | 
| 
      
 76 
     | 
    
         
            +
                    # @since 0.1.0
         
     | 
| 
      
 77 
     | 
    
         
            +
                    def update(collection, entity)
         
     | 
| 
      
 78 
     | 
    
         
            +
                      command(
         
     | 
| 
      
 79 
     | 
    
         
            +
                        _find(collection, entity.id)
         
     | 
| 
      
 80 
     | 
    
         
            +
                      ).update(entity)
         
     | 
| 
      
 81 
     | 
    
         
            +
                    end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                    # Deletes a record in the database corresponding to the given entity.
         
     | 
| 
      
 84 
     | 
    
         
            +
                    #
         
     | 
| 
      
 85 
     | 
    
         
            +
                    # @param collection [Symbol] the target collection (it must be mapped).
         
     | 
| 
      
 86 
     | 
    
         
            +
                    # @param entity [#id] the entity to delete
         
     | 
| 
      
 87 
     | 
    
         
            +
                    #
         
     | 
| 
      
 88 
     | 
    
         
            +
                    # @api private
         
     | 
| 
      
 89 
     | 
    
         
            +
                    # @since 0.1.0
         
     | 
| 
      
 90 
     | 
    
         
            +
                    def delete(collection, entity)
         
     | 
| 
      
 91 
     | 
    
         
            +
                      command(
         
     | 
| 
      
 92 
     | 
    
         
            +
                        _find(collection, entity.id)
         
     | 
| 
      
 93 
     | 
    
         
            +
                      ).delete
         
     | 
| 
      
 94 
     | 
    
         
            +
                    end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                    # Deletes all the records from the given collection.
         
     | 
| 
      
 97 
     | 
    
         
            +
                    #
         
     | 
| 
      
 98 
     | 
    
         
            +
                    # @param collection [Symbol] the target collection (it must be mapped).
         
     | 
| 
      
 99 
     | 
    
         
            +
                    #
         
     | 
| 
      
 100 
     | 
    
         
            +
                    # @api private
         
     | 
| 
      
 101 
     | 
    
         
            +
                    # @since 0.1.0
         
     | 
| 
      
 102 
     | 
    
         
            +
                    def clear(collection)
         
     | 
| 
      
 103 
     | 
    
         
            +
                      command(query(collection)).clear
         
     | 
| 
      
 104 
     | 
    
         
            +
                    end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                    # Fabricates a command for the given query.
         
     | 
| 
      
 107 
     | 
    
         
            +
                    #
         
     | 
| 
      
 108 
     | 
    
         
            +
                    # @param query [Hanami::Model::Adapters::Sql::Query] the query object to
         
     | 
| 
      
 109 
     | 
    
         
            +
                    #   act on.
         
     | 
| 
      
 110 
     | 
    
         
            +
                    #
         
     | 
| 
      
 111 
     | 
    
         
            +
                    # @return [Hanami::Model::Adapters::Sql::Command]
         
     | 
| 
      
 112 
     | 
    
         
            +
                    #
         
     | 
| 
      
 113 
     | 
    
         
            +
                    # @see Hanami::Model::Adapters::Sql::Command
         
     | 
| 
      
 114 
     | 
    
         
            +
                    #
         
     | 
| 
      
 115 
     | 
    
         
            +
                    # @api private
         
     | 
| 
      
 116 
     | 
    
         
            +
                    # @since 0.1.0
         
     | 
| 
      
 117 
     | 
    
         
            +
                    def command(query)
         
     | 
| 
      
 118 
     | 
    
         
            +
                      Sql::Command.new(query)
         
     | 
| 
      
 119 
     | 
    
         
            +
                    end
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
                    # Fabricates a query
         
     | 
| 
      
 122 
     | 
    
         
            +
                    #
         
     | 
| 
      
 123 
     | 
    
         
            +
                    # @param collection [Symbol] the target collection (it must be mapped).
         
     | 
| 
      
 124 
     | 
    
         
            +
                    # @param blk [Proc] a block of code to be executed in the context of
         
     | 
| 
      
 125 
     | 
    
         
            +
                    #   the query.
         
     | 
| 
      
 126 
     | 
    
         
            +
                    #
         
     | 
| 
      
 127 
     | 
    
         
            +
                    # @return [Hanami::Model::Adapters::Sql::Query]
         
     | 
| 
      
 128 
     | 
    
         
            +
                    #
         
     | 
| 
      
 129 
     | 
    
         
            +
                    # @see Hanami::Model::Adapters::Sql::Query
         
     | 
| 
      
 130 
     | 
    
         
            +
                    #
         
     | 
| 
      
 131 
     | 
    
         
            +
                    # @api private
         
     | 
| 
      
 132 
     | 
    
         
            +
                    # @since 0.1.0
         
     | 
| 
      
 133 
     | 
    
         
            +
                    def query(collection, context = nil, &blk)
         
     | 
| 
      
 134 
     | 
    
         
            +
                      Sql::Query.new(_collection(collection), context, &blk)
         
     | 
| 
      
 135 
     | 
    
         
            +
                    end
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                    # Wraps the given block in a transaction.
         
     | 
| 
      
 138 
     | 
    
         
            +
                    #
         
     | 
| 
      
 139 
     | 
    
         
            +
                    # For performance reasons the block isn't in the signature of the method,
         
     | 
| 
      
 140 
     | 
    
         
            +
                    # but it's yielded at the lower level.
         
     | 
| 
      
 141 
     | 
    
         
            +
                    #
         
     | 
| 
      
 142 
     | 
    
         
            +
                    # @param options [Hash] options for transaction
         
     | 
| 
      
 143 
     | 
    
         
            +
                    # @option rollback [Symbol] the optional rollback policy: `:always` or
         
     | 
| 
      
 144 
     | 
    
         
            +
                    #   `:reraise`.
         
     | 
| 
      
 145 
     | 
    
         
            +
                    #
         
     | 
| 
      
 146 
     | 
    
         
            +
                    # @see Hanami::Repository::ClassMethods#transaction
         
     | 
| 
      
 147 
     | 
    
         
            +
                    #
         
     | 
| 
      
 148 
     | 
    
         
            +
                    # @since 0.2.3
         
     | 
| 
      
 149 
     | 
    
         
            +
                    # @api private
         
     | 
| 
      
 150 
     | 
    
         
            +
                    #
         
     | 
| 
      
 151 
     | 
    
         
            +
                    # @example Basic usage
         
     | 
| 
      
 152 
     | 
    
         
            +
                    #   require 'hanami/model'
         
     | 
| 
      
 153 
     | 
    
         
            +
                    #
         
     | 
| 
      
 154 
     | 
    
         
            +
                    #   class Article
         
     | 
| 
      
 155 
     | 
    
         
            +
                    #     include Hanami::Entity
         
     | 
| 
      
 156 
     | 
    
         
            +
                    #     attributes :title, :body
         
     | 
| 
      
 157 
     | 
    
         
            +
                    #   end
         
     | 
| 
      
 158 
     | 
    
         
            +
                    #
         
     | 
| 
      
 159 
     | 
    
         
            +
                    #   class ArticleRepository
         
     | 
| 
      
 160 
     | 
    
         
            +
                    #     include Hanami::Repository
         
     | 
| 
      
 161 
     | 
    
         
            +
                    #   end
         
     | 
| 
      
 162 
     | 
    
         
            +
                    #
         
     | 
| 
      
 163 
     | 
    
         
            +
                    #   article = Article.new(title: 'Introducing transactions',
         
     | 
| 
      
 164 
     | 
    
         
            +
                    #     body: 'lorem ipsum')
         
     | 
| 
      
 165 
     | 
    
         
            +
                    #
         
     | 
| 
      
 166 
     | 
    
         
            +
                    #   ArticleRepository.transaction do
         
     | 
| 
      
 167 
     | 
    
         
            +
                    #     ArticleRepository.dangerous_operation!(article) # => RuntimeError
         
     | 
| 
      
 168 
     | 
    
         
            +
                    #     # !!! ROLLBACK !!!
         
     | 
| 
      
 169 
     | 
    
         
            +
                    #   end
         
     | 
| 
      
 170 
     | 
    
         
            +
                    #
         
     | 
| 
      
 171 
     | 
    
         
            +
                    # @example Policy rollback always
         
     | 
| 
      
 172 
     | 
    
         
            +
                    #   require 'hanami/model'
         
     | 
| 
      
 173 
     | 
    
         
            +
                    #
         
     | 
| 
      
 174 
     | 
    
         
            +
                    #   class Article
         
     | 
| 
      
 175 
     | 
    
         
            +
                    #     include Hanami::Entity
         
     | 
| 
      
 176 
     | 
    
         
            +
                    #     attributes :title, :body
         
     | 
| 
      
 177 
     | 
    
         
            +
                    #   end
         
     | 
| 
      
 178 
     | 
    
         
            +
                    #
         
     | 
| 
      
 179 
     | 
    
         
            +
                    #   class ArticleRepository
         
     | 
| 
      
 180 
     | 
    
         
            +
                    #     include Hanami::Repository
         
     | 
| 
      
 181 
     | 
    
         
            +
                    #   end
         
     | 
| 
      
 182 
     | 
    
         
            +
                    #
         
     | 
| 
      
 183 
     | 
    
         
            +
                    #   article = Article.new(title: 'Introducing transactions',
         
     | 
| 
      
 184 
     | 
    
         
            +
                    #     body: 'lorem ipsum')
         
     | 
| 
      
 185 
     | 
    
         
            +
                    #
         
     | 
| 
      
 186 
     | 
    
         
            +
                    #   ArticleRepository.transaction(rollback: :always) do
         
     | 
| 
      
 187 
     | 
    
         
            +
                    #     ArticleRepository.create(article)
         
     | 
| 
      
 188 
     | 
    
         
            +
                    #     # !!! ROLLBACK !!!
         
     | 
| 
      
 189 
     | 
    
         
            +
                    #   end
         
     | 
| 
      
 190 
     | 
    
         
            +
                    #
         
     | 
| 
      
 191 
     | 
    
         
            +
                    #   # The operation is rolled back, even in no exceptions were raised.
         
     | 
| 
      
 192 
     | 
    
         
            +
                    #
         
     | 
| 
      
 193 
     | 
    
         
            +
                    # @example Policy rollback reraise
         
     | 
| 
      
 194 
     | 
    
         
            +
                    #   require 'hanami/model'
         
     | 
| 
      
 195 
     | 
    
         
            +
                    #
         
     | 
| 
      
 196 
     | 
    
         
            +
                    #   class Article
         
     | 
| 
      
 197 
     | 
    
         
            +
                    #     include Hanami::Entity
         
     | 
| 
      
 198 
     | 
    
         
            +
                    #     attributes :title, :body
         
     | 
| 
      
 199 
     | 
    
         
            +
                    #   end
         
     | 
| 
      
 200 
     | 
    
         
            +
                    #
         
     | 
| 
      
 201 
     | 
    
         
            +
                    #   class ArticleRepository
         
     | 
| 
      
 202 
     | 
    
         
            +
                    #     include Hanami::Repository
         
     | 
| 
      
 203 
     | 
    
         
            +
                    #   end
         
     | 
| 
      
 204 
     | 
    
         
            +
                    #
         
     | 
| 
      
 205 
     | 
    
         
            +
                    #   article = Article.new(title: 'Introducing transactions',
         
     | 
| 
      
 206 
     | 
    
         
            +
                    #     body: 'lorem ipsum')
         
     | 
| 
      
 207 
     | 
    
         
            +
                    #
         
     | 
| 
      
 208 
     | 
    
         
            +
                    #   ArticleRepository.transaction(rollback: :reraise) do
         
     | 
| 
      
 209 
     | 
    
         
            +
                    #     ArticleRepository.dangerous_operation!(article) # => RuntimeError
         
     | 
| 
      
 210 
     | 
    
         
            +
                    #     # !!! ROLLBACK !!!
         
     | 
| 
      
 211 
     | 
    
         
            +
                    #   end # => RuntimeError
         
     | 
| 
      
 212 
     | 
    
         
            +
                    #
         
     | 
| 
      
 213 
     | 
    
         
            +
                    #   # The operation is rolled back, but RuntimeError is re-raised.
         
     | 
| 
      
 214 
     | 
    
         
            +
                    def transaction(options = {})
         
     | 
| 
      
 215 
     | 
    
         
            +
                      @connection.transaction(options) do
         
     | 
| 
      
 216 
     | 
    
         
            +
                        yield
         
     | 
| 
      
 217 
     | 
    
         
            +
                      end
         
     | 
| 
      
 218 
     | 
    
         
            +
                    end
         
     | 
| 
      
 219 
     | 
    
         
            +
             
     | 
| 
      
 220 
     | 
    
         
            +
                    # Returns a string which can be executed to start a console suitable
         
     | 
| 
      
 221 
     | 
    
         
            +
                    # for the configured database, adding the necessary CLI flags, such as
         
     | 
| 
      
 222 
     | 
    
         
            +
                    # url, password, port number etc.
         
     | 
| 
      
 223 
     | 
    
         
            +
                    #
         
     | 
| 
      
 224 
     | 
    
         
            +
                    # @return [String]
         
     | 
| 
      
 225 
     | 
    
         
            +
                    #
         
     | 
| 
      
 226 
     | 
    
         
            +
                    # @since 0.3.0
         
     | 
| 
      
 227 
     | 
    
         
            +
                    def connection_string
         
     | 
| 
      
 228 
     | 
    
         
            +
                      Sql::Console.new(@uri).connection_string
         
     | 
| 
      
 229 
     | 
    
         
            +
                    end
         
     | 
| 
      
 230 
     | 
    
         
            +
             
     | 
| 
      
 231 
     | 
    
         
            +
                    # Executes a raw SQL command
         
     | 
| 
      
 232 
     | 
    
         
            +
                    #
         
     | 
| 
      
 233 
     | 
    
         
            +
                    # @param raw [String] the raw SQL statement to execute on the connection
         
     | 
| 
      
 234 
     | 
    
         
            +
                    #
         
     | 
| 
      
 235 
     | 
    
         
            +
                    # @raise [Hanami::Model::InvalidCommandError] if the raw SQL statement is invalid
         
     | 
| 
      
 236 
     | 
    
         
            +
                    #
         
     | 
| 
      
 237 
     | 
    
         
            +
                    # @return [NilClass]
         
     | 
| 
      
 238 
     | 
    
         
            +
                    #
         
     | 
| 
      
 239 
     | 
    
         
            +
                    # @since 0.3.1
         
     | 
| 
      
 240 
     | 
    
         
            +
                    def execute(raw)
         
     | 
| 
      
 241 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 242 
     | 
    
         
            +
                        @connection.execute(raw)
         
     | 
| 
      
 243 
     | 
    
         
            +
                        nil
         
     | 
| 
      
 244 
     | 
    
         
            +
                      rescue Sequel::DatabaseError => e
         
     | 
| 
      
 245 
     | 
    
         
            +
                        raise Hanami::Model::InvalidCommandError.new(e.message)
         
     | 
| 
      
 246 
     | 
    
         
            +
                      end
         
     | 
| 
      
 247 
     | 
    
         
            +
                    end
         
     | 
| 
      
 248 
     | 
    
         
            +
             
     | 
| 
      
 249 
     | 
    
         
            +
                    # Fetches raw result sets for the given SQL query
         
     | 
| 
      
 250 
     | 
    
         
            +
                    #
         
     | 
| 
      
 251 
     | 
    
         
            +
                    # @param raw [String] the raw SQL query
         
     | 
| 
      
 252 
     | 
    
         
            +
                    # @param blk [Proc] optional block that is yielded for each record
         
     | 
| 
      
 253 
     | 
    
         
            +
                    #
         
     | 
| 
      
 254 
     | 
    
         
            +
                    # @return [Array]
         
     | 
| 
      
 255 
     | 
    
         
            +
                    #
         
     | 
| 
      
 256 
     | 
    
         
            +
                    # @raise [Hanami::Model::InvalidQueryError] if the raw SQL statement is invalid
         
     | 
| 
      
 257 
     | 
    
         
            +
                    #
         
     | 
| 
      
 258 
     | 
    
         
            +
                    # @since 0.5.0
         
     | 
| 
      
 259 
     | 
    
         
            +
                    def fetch(raw, &blk)
         
     | 
| 
      
 260 
     | 
    
         
            +
                      if block_given?
         
     | 
| 
      
 261 
     | 
    
         
            +
                        @connection.fetch(raw, &blk)
         
     | 
| 
      
 262 
     | 
    
         
            +
                      else
         
     | 
| 
      
 263 
     | 
    
         
            +
                        @connection.fetch(raw).to_a
         
     | 
| 
      
 264 
     | 
    
         
            +
                      end
         
     | 
| 
      
 265 
     | 
    
         
            +
                    rescue Sequel::DatabaseError => e
         
     | 
| 
      
 266 
     | 
    
         
            +
                      raise Hanami::Model::InvalidQueryError.new(e.message)
         
     | 
| 
      
 267 
     | 
    
         
            +
                    end
         
     | 
| 
      
 268 
     | 
    
         
            +
             
     | 
| 
      
 269 
     | 
    
         
            +
                    # @api private
         
     | 
| 
      
 270 
     | 
    
         
            +
                    # @since 0.5.0
         
     | 
| 
      
 271 
     | 
    
         
            +
                    #
         
     | 
| 
      
 272 
     | 
    
         
            +
                    # @see Hanami::Model::Adapters::Abstract#disconnect
         
     | 
| 
      
 273 
     | 
    
         
            +
                    def disconnect
         
     | 
| 
      
 274 
     | 
    
         
            +
                      @connection.disconnect
         
     | 
| 
      
 275 
     | 
    
         
            +
                      @connection = DisconnectedResource.new
         
     | 
| 
      
 276 
     | 
    
         
            +
                    end
         
     | 
| 
      
 277 
     | 
    
         
            +
             
     | 
| 
      
 278 
     | 
    
         
            +
                    private
         
     | 
| 
      
 279 
     | 
    
         
            +
             
     | 
| 
      
 280 
     | 
    
         
            +
                    # Returns a collection from the given name.
         
     | 
| 
      
 281 
     | 
    
         
            +
                    #
         
     | 
| 
      
 282 
     | 
    
         
            +
                    # @param name [Symbol] a name of the collection (it must be mapped).
         
     | 
| 
      
 283 
     | 
    
         
            +
                    #
         
     | 
| 
      
 284 
     | 
    
         
            +
                    # @return [Hanami::Model::Adapters::Sql::Collection]
         
     | 
| 
      
 285 
     | 
    
         
            +
                    #
         
     | 
| 
      
 286 
     | 
    
         
            +
                    # @see Hanami::Model::Adapters::Sql::Collection
         
     | 
| 
      
 287 
     | 
    
         
            +
                    #
         
     | 
| 
      
 288 
     | 
    
         
            +
                    # @api private
         
     | 
| 
      
 289 
     | 
    
         
            +
                    # @since 0.1.0
         
     | 
| 
      
 290 
     | 
    
         
            +
                    def _collection(name)
         
     | 
| 
      
 291 
     | 
    
         
            +
                      Sql::Collection.new(@connection[name], _mapped_collection(name))
         
     | 
| 
      
 292 
     | 
    
         
            +
                    end
         
     | 
| 
      
 293 
     | 
    
         
            +
                  end
         
     | 
| 
      
 294 
     | 
    
         
            +
                end
         
     | 
| 
      
 295 
     | 
    
         
            +
              end
         
     | 
| 
      
 296 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,74 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Hanami
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Model
         
     | 
| 
      
 3 
     | 
    
         
            +
                # Abstract coercer
         
     | 
| 
      
 4 
     | 
    
         
            +
                #
         
     | 
| 
      
 5 
     | 
    
         
            +
                # It can be used as super class for custom mapping coercers.
         
     | 
| 
      
 6 
     | 
    
         
            +
                #
         
     | 
| 
      
 7 
     | 
    
         
            +
                # @since 0.5.0
         
     | 
| 
      
 8 
     | 
    
         
            +
                #
         
     | 
| 
      
 9 
     | 
    
         
            +
                # @see Hanami::Model::Mapper
         
     | 
| 
      
 10 
     | 
    
         
            +
                #
         
     | 
| 
      
 11 
     | 
    
         
            +
                # @example Postgres Array
         
     | 
| 
      
 12 
     | 
    
         
            +
                #   require 'hanami/model/coercer'
         
     | 
| 
      
 13 
     | 
    
         
            +
                #   require 'sequel/extensions/pg_array'
         
     | 
| 
      
 14 
     | 
    
         
            +
                #
         
     | 
| 
      
 15 
     | 
    
         
            +
                #   class PGArray < Hanami::Model::Coercer
         
     | 
| 
      
 16 
     | 
    
         
            +
                #     def self.dump(value)
         
     | 
| 
      
 17 
     | 
    
         
            +
                #       ::Sequel.pg_array(value) rescue nil
         
     | 
| 
      
 18 
     | 
    
         
            +
                #     end
         
     | 
| 
      
 19 
     | 
    
         
            +
                #
         
     | 
| 
      
 20 
     | 
    
         
            +
                #     def self.load(value)
         
     | 
| 
      
 21 
     | 
    
         
            +
                #       ::Kernel.Array(value) unless value.nil?
         
     | 
| 
      
 22 
     | 
    
         
            +
                #     end
         
     | 
| 
      
 23 
     | 
    
         
            +
                #   end
         
     | 
| 
      
 24 
     | 
    
         
            +
                #
         
     | 
| 
      
 25 
     | 
    
         
            +
                #   Hanami::Model.configure do
         
     | 
| 
      
 26 
     | 
    
         
            +
                #     mapping do
         
     | 
| 
      
 27 
     | 
    
         
            +
                #       collection :articles do
         
     | 
| 
      
 28 
     | 
    
         
            +
                #         entity     Article
         
     | 
| 
      
 29 
     | 
    
         
            +
                #         repository ArticleRepository
         
     | 
| 
      
 30 
     | 
    
         
            +
                #
         
     | 
| 
      
 31 
     | 
    
         
            +
                #         attribute :id,    Integer
         
     | 
| 
      
 32 
     | 
    
         
            +
                #         attribute :title, String
         
     | 
| 
      
 33 
     | 
    
         
            +
                #         attribute :tags,  PGArray
         
     | 
| 
      
 34 
     | 
    
         
            +
                #       end
         
     | 
| 
      
 35 
     | 
    
         
            +
                #     end
         
     | 
| 
      
 36 
     | 
    
         
            +
                #   end.load!
         
     | 
| 
      
 37 
     | 
    
         
            +
                #
         
     | 
| 
      
 38 
     | 
    
         
            +
                #   # When the entity is serialized, it calls `PGArray.dump` to store `tags`
         
     | 
| 
      
 39 
     | 
    
         
            +
                #   # as a Postgres Array.
         
     | 
| 
      
 40 
     | 
    
         
            +
                #   #
         
     | 
| 
      
 41 
     | 
    
         
            +
                #   # When the record is loaded (unserialized) from the database, it calls
         
     | 
| 
      
 42 
     | 
    
         
            +
                #   # `PGArray.load` and returns a Ruby Array.
         
     | 
| 
      
 43 
     | 
    
         
            +
                class Coercer
         
     | 
| 
      
 44 
     | 
    
         
            +
                  # Deserialize (load) a value coming from the database into a Ruby object.
         
     | 
| 
      
 45 
     | 
    
         
            +
                  #
         
     | 
| 
      
 46 
     | 
    
         
            +
                  # When inheriting from this class, it's a good practice to return <tt>nil</tt>
         
     | 
| 
      
 47 
     | 
    
         
            +
                  # if the given value it's <tt>nil</tt>.
         
     | 
| 
      
 48 
     | 
    
         
            +
                  #
         
     | 
| 
      
 49 
     | 
    
         
            +
                  # @abstract
         
     | 
| 
      
 50 
     | 
    
         
            +
                  #
         
     | 
| 
      
 51 
     | 
    
         
            +
                  # @raise [TypeError] if the value can't be coerced
         
     | 
| 
      
 52 
     | 
    
         
            +
                  #
         
     | 
| 
      
 53 
     | 
    
         
            +
                  # @since 0.5.0
         
     | 
| 
      
 54 
     | 
    
         
            +
                  #
         
     | 
| 
      
 55 
     | 
    
         
            +
                  # @see Hanami::Model::Mapping::Coercers
         
     | 
| 
      
 56 
     | 
    
         
            +
                  def self.load(value)
         
     | 
| 
      
 57 
     | 
    
         
            +
                    raise NotImplementedError
         
     | 
| 
      
 58 
     | 
    
         
            +
                  end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                  # Serialize (dump) a Ruby object into a value that can be store by the database.
         
     | 
| 
      
 61 
     | 
    
         
            +
                  #
         
     | 
| 
      
 62 
     | 
    
         
            +
                  # @abstract
         
     | 
| 
      
 63 
     | 
    
         
            +
                  #
         
     | 
| 
      
 64 
     | 
    
         
            +
                  # @raise [TypeError] if the value can't be coerced
         
     | 
| 
      
 65 
     | 
    
         
            +
                  #
         
     | 
| 
      
 66 
     | 
    
         
            +
                  # @since 0.5.0
         
     | 
| 
      
 67 
     | 
    
         
            +
                  #
         
     | 
| 
      
 68 
     | 
    
         
            +
                  # @see Hanami::Model::Mapping::Coercers
         
     | 
| 
      
 69 
     | 
    
         
            +
                  def self.dump(value)
         
     | 
| 
      
 70 
     | 
    
         
            +
                    self.load(value)
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
              end
         
     | 
| 
      
 74 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,116 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'hanami/utils/class'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Hanami
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Model
         
     | 
| 
      
 5 
     | 
    
         
            +
                module Config
         
     | 
| 
      
 6 
     | 
    
         
            +
                  # Raised when an adapter class does not exist
         
     | 
| 
      
 7 
     | 
    
         
            +
                  #
         
     | 
| 
      
 8 
     | 
    
         
            +
                  # @since 0.2.0
         
     | 
| 
      
 9 
     | 
    
         
            +
                  class AdapterNotFound < Hanami::Model::Error
         
     | 
| 
      
 10 
     | 
    
         
            +
                    def initialize(adapter_name)
         
     | 
| 
      
 11 
     | 
    
         
            +
                      super "Cannot find Hanami::Model adapter #{adapter_name}"
         
     | 
| 
      
 12 
     | 
    
         
            +
                    end
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  # Configuration for the adapter
         
     | 
| 
      
 16 
     | 
    
         
            +
                  #
         
     | 
| 
      
 17 
     | 
    
         
            +
                  # Hanami::Model has its own global configuration that can be manipulated
         
     | 
| 
      
 18 
     | 
    
         
            +
                  # via `Hanami::Model.configure`.
         
     | 
| 
      
 19 
     | 
    
         
            +
                  #
         
     | 
| 
      
 20 
     | 
    
         
            +
                  # New adapter configuration can be registered via `Hanami::Model.adapter`.
         
     | 
| 
      
 21 
     | 
    
         
            +
                  #
         
     | 
| 
      
 22 
     | 
    
         
            +
                  # @see Hanami::Model.adapter
         
     | 
| 
      
 23 
     | 
    
         
            +
                  #
         
     | 
| 
      
 24 
     | 
    
         
            +
                  # @example
         
     | 
| 
      
 25 
     | 
    
         
            +
                  #   require 'hanami/model'
         
     | 
| 
      
 26 
     | 
    
         
            +
                  #
         
     | 
| 
      
 27 
     | 
    
         
            +
                  #   Hanami::Model.configure do
         
     | 
| 
      
 28 
     | 
    
         
            +
                  #     adapter type: :sql, uri: 'postgres://localhost/database'
         
     | 
| 
      
 29 
     | 
    
         
            +
                  #   end
         
     | 
| 
      
 30 
     | 
    
         
            +
                  #
         
     | 
| 
      
 31 
     | 
    
         
            +
                  #   Hanami::Model.configuration.adapter_config
         
     | 
| 
      
 32 
     | 
    
         
            +
                  #   # => Hanami::Model::Config::Adapter(type: :sql, uri: 'postgres://localhost/database')
         
     | 
| 
      
 33 
     | 
    
         
            +
                  #
         
     | 
| 
      
 34 
     | 
    
         
            +
                  # By convention, Hanami inflects type to find the adapter class
         
     | 
| 
      
 35 
     | 
    
         
            +
                  # For example, if type is :sql, derived class will be `Hanami::Model::Adapters::SqlAdapter`
         
     | 
| 
      
 36 
     | 
    
         
            +
                  #
         
     | 
| 
      
 37 
     | 
    
         
            +
                  # @since 0.2.0
         
     | 
| 
      
 38 
     | 
    
         
            +
                  class Adapter
         
     | 
| 
      
 39 
     | 
    
         
            +
                    # @return [Symbol] the adapter name
         
     | 
| 
      
 40 
     | 
    
         
            +
                    #
         
     | 
| 
      
 41 
     | 
    
         
            +
                    # @since 0.2.0
         
     | 
| 
      
 42 
     | 
    
         
            +
                    attr_reader :type
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                    # @return [String] the adapter URI
         
     | 
| 
      
 45 
     | 
    
         
            +
                    #
         
     | 
| 
      
 46 
     | 
    
         
            +
                    # @since 0.2.0
         
     | 
| 
      
 47 
     | 
    
         
            +
                    attr_reader :uri
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                    # @return [Hash] a list of non-mandatory options for the adapter
         
     | 
| 
      
 50 
     | 
    
         
            +
                    #
         
     | 
| 
      
 51 
     | 
    
         
            +
                    attr_reader :options
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                    # @return [String] the adapter class name
         
     | 
| 
      
 54 
     | 
    
         
            +
                    #
         
     | 
| 
      
 55 
     | 
    
         
            +
                    # @since 0.2.0
         
     | 
| 
      
 56 
     | 
    
         
            +
                    attr_reader :class_name
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                    # Initialize an adapter configuration instance
         
     | 
| 
      
 59 
     | 
    
         
            +
                    #
         
     | 
| 
      
 60 
     | 
    
         
            +
                    # @param options [Hash] configuration options
         
     | 
| 
      
 61 
     | 
    
         
            +
                    # @option options [Symbol] :type adapter type name
         
     | 
| 
      
 62 
     | 
    
         
            +
                    # @option options [String] :uri adapter URI
         
     | 
| 
      
 63 
     | 
    
         
            +
                    #
         
     | 
| 
      
 64 
     | 
    
         
            +
                    # @return [Hanami::Model::Config::Adapter] a new apdapter configuration's
         
     | 
| 
      
 65 
     | 
    
         
            +
                    #   instance
         
     | 
| 
      
 66 
     | 
    
         
            +
                    #
         
     | 
| 
      
 67 
     | 
    
         
            +
                    # @since 0.2.0
         
     | 
| 
      
 68 
     | 
    
         
            +
                    def initialize(**options)
         
     | 
| 
      
 69 
     | 
    
         
            +
                      opts     = options.dup
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                      @type    = opts.delete(:type)
         
     | 
| 
      
 72 
     | 
    
         
            +
                      @uri     = opts.delete(:uri)
         
     | 
| 
      
 73 
     | 
    
         
            +
                      @options = opts
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                      @class_name ||= Hanami::Utils::String.new("#{@type}_adapter").classify
         
     | 
| 
      
 76 
     | 
    
         
            +
                    end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                    # Initialize the adapter
         
     | 
| 
      
 79 
     | 
    
         
            +
                    #
         
     | 
| 
      
 80 
     | 
    
         
            +
                    # @param mapper [Hanami::Model::Mapper] the mapper instance
         
     | 
| 
      
 81 
     | 
    
         
            +
                    #
         
     | 
| 
      
 82 
     | 
    
         
            +
                    # @return [Hanami::Model::Adapters::SqlAdapter, Hanami::Model::Adapters::MemoryAdapter] an adapter instance
         
     | 
| 
      
 83 
     | 
    
         
            +
                    #
         
     | 
| 
      
 84 
     | 
    
         
            +
                    # @see Hanami::Model::Adapters
         
     | 
| 
      
 85 
     | 
    
         
            +
                    #
         
     | 
| 
      
 86 
     | 
    
         
            +
                    # @since 0.2.0
         
     | 
| 
      
 87 
     | 
    
         
            +
                    def build(mapper)
         
     | 
| 
      
 88 
     | 
    
         
            +
                      load_adapter
         
     | 
| 
      
 89 
     | 
    
         
            +
                      instantiate_adapter(mapper)
         
     | 
| 
      
 90 
     | 
    
         
            +
                    end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                    private
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                    def load_adapter
         
     | 
| 
      
 95 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 96 
     | 
    
         
            +
                        require "hanami/model/adapters/#{type}_adapter"
         
     | 
| 
      
 97 
     | 
    
         
            +
                      rescue LoadError => e
         
     | 
| 
      
 98 
     | 
    
         
            +
                        raise LoadError.new("Cannot find Hanami::Model adapter '#{type}' (#{e.message})")
         
     | 
| 
      
 99 
     | 
    
         
            +
                      end
         
     | 
| 
      
 100 
     | 
    
         
            +
                    end
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                    def instantiate_adapter(mapper)
         
     | 
| 
      
 103 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 104 
     | 
    
         
            +
                        klass = Hanami::Utils::Class.load!(class_name, Hanami::Model::Adapters)
         
     | 
| 
      
 105 
     | 
    
         
            +
                        klass.new(mapper, uri, options)
         
     | 
| 
      
 106 
     | 
    
         
            +
                      rescue NameError
         
     | 
| 
      
 107 
     | 
    
         
            +
                        raise AdapterNotFound.new(class_name)
         
     | 
| 
      
 108 
     | 
    
         
            +
                      rescue => e
         
     | 
| 
      
 109 
     | 
    
         
            +
                        raise "Cannot instantiate adapter of #{klass} (#{e.message})"
         
     | 
| 
      
 110 
     | 
    
         
            +
                      end
         
     | 
| 
      
 111 
     | 
    
         
            +
                    end
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                  end
         
     | 
| 
      
 114 
     | 
    
         
            +
                end
         
     | 
| 
      
 115 
     | 
    
         
            +
              end
         
     | 
| 
      
 116 
     | 
    
         
            +
            end
         
     |