[
  {
    "path": ".github/FUNDING.yml",
    "content": "github: hanami\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: ci\n\n\"on\":\n  push:\n    paths:\n      - \".github/workflows/ci.yml\"\n      - \"lib/**\"\n      - \"*.gemspec\"\n      - \"spec/**\"\n      - \"Rakefile\"\n      - \"Gemfile\"\n      - \".rubocop.yml\"\n      - \"script/ci\"\n  pull_request:\n    branches:\n      - main\n  create:\n\njobs:\n  tests:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        ruby:\n          - \"2.7\"\n        db:\n          - sqlite3\n          - mysql\n          - postgresql\n    env:\n      DB: ${{matrix.db}}\n    steps:\n      - uses: actions/checkout@v1\n      - name: Install package dependencies\n        run: \"[ -e $APT_DEPS ] || sudo apt-get install -y --no-install-recommends $APT_DEPS\"\n      - name: Set up Ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          bundler-cache: true\n          ruby-version: ${{matrix.ruby}}\n      - name: Run all tests\n        env:\n          HANAMI_DATABASE_USERNAME: root\n          HANAMI_DATABASE_PASSWORD: root\n          HANAMI_DATABASE_HOST: 127.0.0.1\n          HANAMI_DATABASE_NAME: hanami_model\n        run: script/ci\n    services:\n      mysql:\n        image: mysql:8\n        env:\n          ALLOW_EMPTY_PASSWORD: true\n          MYSQL_AUTHENTICATION_PLUGIN: mysql_native_password\n          MYSQL_ROOT_PASSWORD: root\n          MYSQL_DATABASE: hanami_model\n        ports:\n          - 3306:3306\n        options: >-\n          --health-cmd=\"mysqladmin ping\"\n          --health-interval=10s\n          --health-timeout=5s\n          --health-retries=3\n      postgres:\n        image: postgres:13\n        ports:\n          - 5432:5432\n        options: >-\n          --health-cmd pg_isready\n          --health-interval 10s\n          --health-timeout 5s\n          --health-retries 5\n        env:\n          POSTGRES_USER: root\n          POSTGRES_PASSWORD: root\n          POSTGRES_DB: hanami_model\n"
  },
  {
    "path": ".gitignore",
    "content": "*.gem\n*.rbc\n.bundle\n.config\n.DS_Store\n.greenbar\n.ruby-version\n.yardoc\n.rubocop-*\n_yardoc\ncoverage\ndoc/\nGemfile.lock\nInstalledFiles\nlib/bundler/man\npkg\nrdoc\nspec/reports\ntest/tmp\ntest/version_tmp\ntmp\n"
  },
  {
    "path": ".jrubyrc",
    "content": "debug.fullTrace=true\n"
  },
  {
    "path": ".rspec",
    "content": "--color\n--require spec_helper\n"
  },
  {
    "path": ".rubocop.yml",
    "content": "# Please keep AllCops, Bundler, Style, Metrics groups and then order cops\n# alphabetically\ninherit_from:\n  - https://raw.githubusercontent.com/hanami/devtools/1.3.x/.rubocop.yml\nNaming/MethodParameterName:\n  AllowedNames:\n    - ci\n    - db\n    - id\n    - os\nLayout/LineLength:\n  Enabled: false\nNaming/RescuedExceptionsVariableName:\n  PreferredName: \"exception\"\nStyle/RescueStandardError:\n  Enabled: false\nStyle/DateTime:\n  Enabled: false\n"
  },
  {
    "path": ".yardopts",
    "content": "--protected\n--private\n-\nLICENSE.md\nlib/**/*.rb\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Hanami::Model\nA persistence layer for Hanami\n\n## v1.3.3 - 2021-05-22\n### Fixed\n- [Sean Collins] Specify dependency on BigDecimal v1.4\n- [Adam Daniels] Use environment variables for PostgreSQL CLI tools\n\n## v1.3.2 - 2019-01-31\n### Fixed\n- [Luca Guidi] Depend on `dry-logic` `~> 0.4.2`, `< 0.5`\n\n## v1.3.1 - 2019-01-18\n### Added\n- [Luca Guidi] Official support for Ruby: MRI 2.6\n- [Luca Guidi] Support `bundler` 2.0+\n\n## v1.3.0 - 2018-10-24\n\n## v1.3.0.beta1 - 2018-08-08\n### Fixed\n- [Luca Guidi] Print meaningful error message when connection URL is misconfigured (eg. `Unknown database adapter for URL: \"\". Please check your database configuration (hint: ENV['DATABASE_URL']).`)\n- [Ian Ker-Seymer] Reliably parse query params from connection string\n\n## v1.2.0 - 2018-04-11\n\n## v1.2.0.rc2 - 2018-04-06\n\n## v1.2.0.rc1 - 2018-03-30\n### Fixed\n- [Marcello Rocha & Luca Guidi] Ensure repository relations to access database attributes via `#[]` (eg. `projects[:name].ilike(\"Hanami\")`)\n\n## v1.2.0.beta2 - 2018-03-23\n\n## v1.2.0.beta1 - 2018-02-28\n### Added\n- [Luca Guidi] Official support for Ruby: MRI 2.5\n- [Marcello Rocha] Introduce `Hanami::Repository#command` as a factory for custom database commands. This is useful to create custom bulk operations.\n\n## v1.1.0 - 2017-10-25\n### Fixed\n- [Luca Guidi] Ensure associations to always accept objects that are serializable into `::Hash`\n\n## v1.1.0.rc1 - 2017-10-16\n### Added\n- [Marcello Rocha] Added support for associations aliasing via `:as` option (`has_many :users, through: :comments, as: :authors`)\n- [Luca Guidi] Allow entities to be used as type in entities manual schema (`attribute :owner, Types::Entity(User)`)\n\n## v1.1.0.beta3 - 2017-10-04\n\n## v1.1.0.beta2 - 2017-10-03\n### Added\n- [Alfonso Uceda] Introduce `Hanami::Model::Migrator#rollback` to provide database migrations rollback\n- [Alfonso Uceda] Improve connection string for PostgreSQL in order to pass credentials as URI query string\n\n### Fixed\n- [Marcello Rocha] One-To-Many properly destroy the associated methods\n\n## v1.1.0.beta1 - 2017-08-11\n### Added\n- [Marcello Rocha] Many-To-One association (aka `belongs_to`)\n- [Marcello Rocha] One-To-One association (aka `has_one`)\n- [Marcello Rocha] Many-To-Many association (aka `has_many :through`)\n- [Luca Guidi] Introduced new extra behaviors for entity manual schema: `:schema` (default), `:strict`, `:weak`, and `:permissive`\n\n### Fixed\n- [Sean Collins] Enhanced error message for Postgres `db create` and `db drop` when `createdb` and `dropdb` aren't in `PATH`\n\n## v1.0.4 - 2017-10-14\n### Fixed\n- [Nikita Shilnikov] Keep the dependency on `rom-sql` at `~> 1.3`, which is compatible with `dry-types` `~> 0.11.0`\n- [Nikita Shilnikov] Ensure to write Postgres JSON (`PGJSON`) type for nested associated records\n- [Nikita Shilnikov] Ensure `Repository#select` to work with `Hanami::Model::MappedRelation`\n\n## v1.0.3 - 2017-10-11\n### Fixed\n- [Luca Guidi] Keep the dependency on `dry-types` at `~> 0.11.0`\n\n## v1.0.2 - 2017-08-04\n### Fixed\n- [Maurizio De Magnis] URI escape for Postgres password\n- [Marion Duprey] Ensure repository to generate timestamps values even when only one between `created_at` and `updated_at` is present\n- [Paweł Świątkowski] Make Postgres JSON(B) to work with Ruby arrays\n- [Luca Guidi] Don't remove migrations when running `Hanami::Model::Migrator#apply` fails to dump the database\n\n## v1.0.1 - 2017-06-23\n### Fixed\n- [Kai Kuchenbecker & Marcello Rocha & Luca Guidi] Ensure `Hanami::Entity#initialize` to not serialize (into `Hash`) other entities passed as an argument\n- [Luca Guidi] Let `Hanami::Repository.relation=` to accept strings as an argument\n- [Nikita Shilnikov] Prevent stack-overflow when `Hanami::Repository#update` is called thousand times\n\n## v1.0.0 - 2017-04-06\n\n## v1.0.0.rc1 - 2017-03-31\n\n## v1.0.0.beta3 - 2017-03-17\n### Added\n- [Luca Guidi] Introduced `Hanami::Model.disconnect` to disconnect all the active database connections\n\n## v1.0.0.beta2 - 2017-03-02\n### Added\n- [Semyon Pupkov] Allow to define Postgres connection URL as `\"postgresql:///mydb?host=localhost&port=6433&user=postgres&password=testpasswd\"`\n\n### Fixed\n- [Marcello Rocha] Fixed migrations MySQL detection of username and password\n- [Luca Guidi] Fixed migrations creation/drop of a MySQL database with a dash in the name\n- [Semyon Pupkov] Ensure `db console` to work when Postgres connection URL is defined with `\"postgresql://\"` scheme\n\n## v1.0.0.beta1 - 2017-02-14\n### Added\n- [Luca Guidi] Official support for Ruby: MRI 2.4\n- [Luca Guidi] Introduced `Repository#read` to fetch from database with raw SQL string\n- [Luca Guidi] Introduced `Repository.schema` to manually configure the schema of a database table. This is useful for legacy databases where Hanami::Model autoinferring doesn't map correctly the schema.\n- [Luca Guidi & Alfonso Uceda] Added `Hanami::Model::Configuration#gateway` to configure gateway and the raw connection\n- [Luca Guidi] Added `Hanami::Model::Configuration#logger` to configure a logger\n- [Luca Guidi] Database operations (including migrations) print informations to standard output\n\n### Fixed\n- [Thorbjørn Hermansen] Ensure repository to not override given timestamps\n- [Luca Guidi] Raise `Hanami::Model::MissingPrimaryKeyError` if `Repository#find` is ran against a database w/o a primary key\n- [Alfonso Uceda] Ensure SQLite databases to be used on JRuby when the database path is in the same directory of the Ruby script (eg. `./test.sqlite`)\n\n### Changed\n- [Luca Guidi] Automap the main relation in a repository, by removing the need of use `.as(:entity)`\n- [Luca Guidi] Raise an `Hanami::Model::UnknownDatabaseTypeError` when the application is loaded and there is an unknown column type in the database\n\n## v0.7.0 - 2016-11-15\n### Added\n- [Luca Guidi] `Hanami::Entity` defines an automatic schema for SQL databases\n– [Luca Guidi] `Hanami::Entity` attributes schema\n- [Luca Guidi] Experimental support for One-To-Many association (aka `has_many`)\n- [Luca Guidi] Native support for PostgreSQL types like UUID, Array, JSON(B) and Money\n- [Luca Guidi] Repositories instances can access all the relations (eg. `BookRepository` can access `users` relation via `#users`)\n- [Luca Guidi] Automapping for SQL databases\n- [Luca Guidi] Added `Hanami::Model::DatabaseError`\n\n### Changed\n- [Luca Guidi] Entities are immutable\n- [Luca Guidi] Removed support for Memory and File System adapters\n- [Luca Guidi] Removed support for _dirty tracking_\n- [Luca Guidi] `Hanami::Entity.attributes` method no longer accepts a list of attributes, but a block to optionally define typed attributes\n- [Luca Guidi] Removed `#fetch`, `#execute` and `#transaction` from repository\n- [Luca Guidi] Removed `mapping` block from `Hanami::Model.configure`\n- [Luca Guidi] Changed `adapter` signature in `Hanami::Model.configure` (use `adapter :sql, ENV['DATABASE_URL']`)\n- [Luca Guidi] Repositories must inherit from `Hanami::Repository` instead of including it\n- [Luca Guidi] Entities must inherit from `Hanami::Entity` instead of including it\n- [Pascal Betz] Repositories use instance level interface (eg. `BookRepository.new.find` instead of `BookRepository.find`)\n- [Luca Guidi] Repositories now accept hashes for CRUD operations\n- [Luca Guidi] `Hanami::Repository#create` now accepts: hash (or entity)\n- [Luca Guidi] `Hanami::Repository#update` now accepts two arguments: primary key (`id`) and data (or entity)\n- [Luca Guidi] `Hanami::Repository#delete` now accepts: primary key (`id`)\n- [Luca Guidi] Drop `Hanami::Model::NonPersistedEntityError`, `Hanami::Model::InvalidMappingError`, `Hanami::Model::InvalidCommandError`, `Hanami::Model::InvalidQueryError`\n- [Luca Guidi] Official support for Ruby 2.3 and JRuby 9.0.5.0\n- [Luca Guidi] Drop support for Ruby 2.0, 2.1, 2.2, and JRuby 9.0.0.0\n- [Luca Guidi] Drop support for `mysql` gem in favor of `mysql2`\n\n### Fixed\n- [Luca Guidi] Ensure booleans to be correctly dumped in database\n- [Luca Guidi] Ensure to respect default database schema values\n- [Luca Guidi] Ensure SQL UPDATE to not override non-default primary key\n- [James Hamilton] Print appropriate error message when trying to create a PostgreSQL database that is already existing\n\n## v0.6.2 - 2016-06-01\n### Changed\n- [Kjell-Magne Øierud] Ensure inherited entities to expose attributes from base class\n\n## v0.6.1 - 2016-02-05\n### Changed\n- [Hélio Costa e Silva & Pascal Betz] Mapping SQL Adapter's errors as `Hanami::Model` errors\n\n## v0.6.1 - 2016-02-05\n### Changed\n- [Hélio Costa e Silva & Pascal Betz] Mapping SQL Adapter's errors as `Hanami::Model` errors\n\n## v0.6.0 - 2016-01-22\n### Changed\n- [Luca Guidi] Renamed the project\n\n## v0.5.2 - 2016-01-19\n### Changed\n- [Sean Collins] Improved error message for `Lotus::Model::Adapters::NoAdapterError`\n\n### Fixed\n- [Kyle Chong & Trung Lê] Catch Sequel exceptions and re-raise as `Lotus::Model::Error`\n\n## v0.5.1 - 2016-01-12\n### Added\n- [Taylor Finnell] Let `Lotus::Model::Configuration#adapter` to accept arbitrary options (eg. `adapter type: :sql, uri: 'jdbc:...', after_connect: Proc.new { |connection| connection.auto_commit(true) }`)\n\n### Changed\n- [Andrey Deryabin] Improved `Entity#inspect`\n- [Karim Tarek] Introduced `Lotus::Model::Error` and let all the framework exceptions to inherit from it.\n\n### Fixed\n- [Luca Guidi] Improved error message when trying to use a repository without mapping the corresponding collections\n- [Sean Collins] Improved error message when trying to create database, but it fails (eg. missing `createdb` executable)\n- [Andrey Deryabin] Improved error message when trying to drop database, but a client is still connected (useful for PostgreSQL)\n- [Hiếu Nguyễn] Improved error message when trying to \"prepare\" database, but it fails\n\n## v0.5.0 - 2015-09-30\n### Added\n- [Brenno Costa] Official support for JRuby 9k+\n- [Luca Guidi] Command/Query separation via `Repository.execute` and `Repository.fetch`\n- [Luca Guidi] Custom attribute coercers for data mapper\n- [Alfonso Uceda] Added `#join` and `#left_join` and `#group` to SQL adapter\n\n### Changed\n- [Luca Guidi] `Repository.execute` no longer returns a result from the database.\n\n### Fixed\n- [Manuel Corrales] Use `dropdb` to drop PostgreSQL database.\n- [Luca Guidi & Bohdan V.] Ignore dotfiles while running migrations.\n\n## v0.4.1 - 2015-07-10\n### Fixed\n- [Nick Coyne] Fixed database creation for PostgreSQL (now it uses `createdb`).\n\n## v0.4.0 - 2015-06-23\n### Added\n- [Luca Guidi] Database migrations\n\n### Changed\n- [Matthew Bellantoni] Made `Repository.execute` not callable from the outside (private Ruby method, public API).\n\n## v0.3.2 - 2015-05-22\n### Added\n- [Dmitry Tymchuk & Luca Guidi] Fix for dirty tracking of attributes changed in place (eg. `book.tags << 'non-fiction'`)\n\n## v0.3.1 - 2015-05-15\n### Added\n- [Dmitry Tymchuk] Dirty tracking for entities (via `Lotus::Entity::DirtyTracking` module to include)\n- [My Mai] Automatic update of timestamps when an entity is persisted.\n- [Peter Berkenbosch] Introduced `Lotus::Repository#execute`, to execute raw query/commands against database (eg. `BookRepository.execute \"SELECT * FROM users\"` or `BookRepository.execute \"UPDATE users SET admin = 'f'\"`)\n- [Guilherme Franco] Memory and File System adapters now accept a block for `where`, `or`, `and` conditions (eg `where { age > 33 }`).\n\n### Fixed\n- [Luca Guidi] Ensure Array coercion to preserve original data structure\n\n## v0.3.0 - 2015-03-23\n### Added\n- [Linus Pettersson] Database console\n\n### Fixed\n- [Alfonso Uceda Pompa] Don't send unwanted null values to the database, while coercing entities\n- [Jan Lelis] Do not define top-level `Boolean`, because it is already defined by `hanami-utils`\n- [Vsevolod Romashov] Fix entity class resolving in `Coercer#from_record`\n- [Jason Harrelson] Add file and line to `instance_eval` in `Coercer` to make backtrace more usable\n\n## v0.2.4 - 2015-02-20\n### Fixed\n- [Luca Guidi] When duplicate the framework don't copy over the original `Lotus::Model` configuration\n\n## v0.2.3 - 2015-02-13\n### Added\n- [Alfonso Uceda Pompa] Added support for database transactions in repositories\n\n### Fixed\n- [Luca Guidi] Ensure file system adapter old data is read when a new process is started\n\n## v0.2.2 - 2015-01-18\n### Added\n- [Luca Guidi] Coerce entities when persisted\n\n## v0.2.1 - 2015-01-12\n### Added\n- [Luca Guidi] Compatibility between Lotus::Entity and Lotus::Validations\n\n## v0.2.0 - 2014-12-23\n### Added\n- [Luca Guidi] Introduced file system adapter\n– [Benny Klotz & Trung Lê] Introduced `Entity` inheritance of attributes\n- [Trung Lê] Introduced `Entity#update` for bulk update of attributes\n- [Luca Guidi] Improved error when try to use a repository which wasn't configured or when the framework wasn't loaded yet\n- [Trung Lê] Introduced `Entity#to_h`\n- [Trung Lê] Introduced `Lotus::Model.duplicate`\n- [Trung Lê] Made `Lotus::Mapper` lazy\n- [Trung Lê] Introduced thread safe autoloading for adapters\n- [Felipe Sere] Add support for `Symbol` coercion\n- [Celso Fernandes] Add support for `BigDecimal` coercion\n- [Trung Lê] Introduced `Lotus::Model.load!` as entry point for loading\n- [Trung Lê] Introduced `Mapper#repository` as DSL to associate a repository to a collection\n- [Trung Lê & Tao Guo] Introduced `Configuration#mapping` as DSL to configure the mapping\n- [Coen Wessels] Allow `where`, `exclude` and `or` to accept blocks\n- [Trung Lê & Tao Guo] Introduced `Configuration#adapter` as DSL to configure the adapter\n- [Trung Lê] Introduced `Lotus::Model::Configuration`\n\n### Changed\n- [Trung Lê] Changed `Entity.attributes=` to `Entity.attributes`\n- [Trung Lê] In case of missing entity, let `Repository#find` returns `nil` instead of raise an exception\n\n### Fixed\n- [Rik Tonnard] Ensure correct behavior of `#offset` in memory adapter\n- [Benny Klotz] Ensure `Entity` to set the attributes even when the given Hash uses strings as keys\n- [Ben Askins] Always return the entity from `Repository#persist`\n- [Jeremy Stephens] Made `Memory::Query#where` and `#or` behave more like the SQL counter-part\n\n## v0.1.2 - 2014-06-26\n### Fixed\n- [Stanislav Spiridonov] Ensure to require `'hanami/model/mapping/coercions'`\n- [Krzysztof Zalewski] `Entity` defines `#id` accessor by default\n\n\n## v0.1.1 - 2014-06-23\n### Added\n- [Luca Guidi] Introduced `Lotus::Model::Mapping::Coercions` in order to decouple from `Lotus::Utils::Kernel`\n- [Luca Guidi] Official support for Ruby 2.1\n\n## v0.1.0 - 2014-04-23\n### Added\n- [Luca Guidi] Allow to inject coercer into mapper\n- [Luca Guidi] Introduced database mapping\n- [Luca Guidi] Introduced `Lotus::Entity`\n- [Luca Guidi] Introduced SQL adapter\n- [Luca Guidi] Introduced memory adapter\n– [Luca Guidi] Introduced adapters for repositories\n- [Luca Guidi] Introduced `Lotus::Repository`\n"
  },
  {
    "path": "Gemfile",
    "content": "# frozen_string_literal: true\n\nsource \"https://rubygems.org\"\ngemspec\n\nunless ENV[\"CI\"]\n  gem \"byebug\", require: false, platforms: :mri\n  gem \"yard\",   require: false\nend\n\ngem \"hanami-utils\", \"~> 1.3\", require: false, git: \"https://github.com/hanami/utils.git\", branch: \"1.3.x\"\n\ngem \"sqlite3\", require: false, platforms: :mri, group: :sqlite\ngem \"pg\",      require: false, platforms: :mri, group: :postgres\ngem \"mysql2\",  require: false, platforms: :mri, group: :mysql\n\ngem \"jdbc-sqlite3\",  require: false, platforms: :jruby, group: :sqlite\ngem \"jdbc-postgres\", require: false, platforms: :jruby, group: :postgres\ngem \"jdbc-mysql\",    require: false, platforms: :jruby, group: :mysql\n\ngem \"hanami-devtools\", require: false, git: \"https://github.com/hanami/devtools.git\", branch: \"1.3.x\"\n"
  },
  {
    "path": "LICENSE.md",
    "content": "Copyright © 2014-2021 Luca Guidi\n\nMIT License\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Hanami::Model (deprecated)\n\n## _Important notice_\n**NOTE**: Hanami::Model was the persistence layer for Hanami 1.x. This library will not receive any updates.\n\nFor the persistence layer for Hanami 2.x, please see [`hanami/db`](https://github.com/hanami/db)\n\n## Contact\n\n* Home page: http://hanamirb.org\n* Mailing List: http://hanamirb.org/mailing-list\n* API Doc: http://rubydoc.info/gems/hanami-model\n* Chat: https://chat.hanamirb.org\n\n## Rubies\n\n`Hanami::Model` **supports Hanami 1.x only**, and Ruby (MRI) 2.6 and 2.7.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'hanami-model'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install hanami-model\n\n## Usage\n\nThis class provides a DSL to configure the connection.\n\n```ruby\nrequire 'hanami/model'\nrequire 'hanami/model/sql'\n\nclass User < Hanami::Entity\nend\n\nclass UserRepository < Hanami::Repository\nend\n\nHanami::Model.configure do\n  adapter :sql, 'postgres://username:password@localhost/bookshelf'\nend.load!\n\nrepository = UserRepository.new\nuser       = repository.create(name: 'Luca')\n\nputs user.id # => 1\n\nfound = repository.find(user.id)\nfound == user # => true\n\nupdated = repository.update(user.id, age: 34)\nupdated.age # => 34\n\nrepository.delete(user.id)\n```\n\n## Concepts\n\n### Entities\n\nA model domain object that is defined by its identity.\nSee \"Domain Driven Design\" by Eric Evans.\n\nAn entity is the core of an application, where the part of the domain logic is implemented.\nIt's a small, cohesive object that expresses coherent and meaningful behaviors.\n\nIt deals with one and only one responsibility that is pertinent to the\ndomain of the application, without caring about details such as persistence\nor validations.\n\nThis simplicity of design allows developers to focus on behaviors, or\nmessage passing if you will, which is the quintessence of Object Oriented Programming.\n\n```ruby\nrequire 'hanami/model'\n\nclass Person < Hanami::Entity\nend\n```\n\n### Repositories\n\nAn object that mediates between entities and the persistence layer.\nIt offers a standardized API to query and execute commands on a database.\n\nA repository is **storage independent**, all the queries and commands are\ndelegated to the current adapter.\n\nThis architecture has several advantages:\n\n  * Applications depend on a standard API, instead of low level details\n    (Dependency Inversion principle)\n\n  * Applications depend on a stable API, that doesn't change if the\n    storage changes\n\n  * Developers can postpone storage decisions\n\n  * Confines persistence logic at a low level\n\n  * Multiple data sources can easily coexist in an application\n\nWhen a class inherits from `Hanami::Repository`, it will receive the following interface:\n\n  * `#create(data)`     – Create a record for the given data (or entity)\n  * `#update(id, data)` – Update the record corresponding to the given id by setting the given data (or entity)\n  * `#delete(id)`       – Delete the record corresponding to the given id\n  * `#all`              - Fetch all the entities from the relation\n  * `#find`             - Fetch an entity from the relation by primary key\n  * `#first`            - Fetch the first entity from the relation\n  * `#last`             - Fetch the last entity from the relation\n  * `#clear`            - Delete all the records from the relation\n\n**A relation is a homogenous set of records.**\nIt corresponds to a table for a SQL database or to a MongoDB collection.\n\n**All the queries are private**.\nThis decision forces developers to define intention revealing API, instead of leaking storage API details outside of a repository.\n\nLook at the following code:\n\n```ruby\nArticleRepository.new.where(author_id: 23).order(:published_at).limit(8)\n```\n\nThis is **bad** for a variety of reasons:\n\n  * The caller has an intimate knowledge of the internal mechanisms of the Repository.\n\n  * The caller works on several levels of abstraction.\n\n  * It doesn't express a clear intent, it's just a chain of methods.\n\n  * The caller can't be easily tested in isolation.\n\n  * If we change the storage, we are forced to change the code of the caller(s).\n\nThere is a better way:\n\n```ruby\nrequire 'hanami/model'\n\nclass ArticleRepository < Hanami::Repository\n  def most_recent_by_author(author, limit: 8)\n    articles.where(author_id: author.id).\n      order(:published_at).\n      limit(limit)\n  end\nend\n```\n\nThis is a **huge improvement**, because:\n\n  * The caller doesn't know how the repository fetches the entities.\n\n  * The caller works on a single level of abstraction. It doesn't even know about records, only works with entities.\n\n  * It expresses a clear intent.\n\n  * The caller can be easily tested in isolation. It's just a matter of stubbing this method.\n\n  * If we change the storage, the callers aren't affected.\n\n### Mapping\n\nHanami::Model can **_automap_** columns from relations and entities attributes.\n\nWhen using a `sql` adapter, you must require `hanami/model/sql` before `Hanami::Model.load!` is called so the relations are loaded correctly.\n\nHowever, there are cases where columns and attribute names do not match (mainly **legacy databases**).\n\n```ruby\nrequire 'hanami/model'\n\nclass UserRepository < Hanami::Repository\n  self.relation = :t_user_archive\n\n  mapping do\n    attribute :id,   from: :i_user_id\n    attribute :name, from: :s_name\n    attribute :age,  from: :i_age\n  end\nend\n```\n**NOTE:** This feature should be used only when **_automapping_** fails because of the naming mismatch.\n\n### Conventions\n\n  * A repository must be named after an entity, by appending `\"Repository\"` to the entity class name (eg. `Article` => `ArticleRepository`).\n\n### Thread safety\n\n**Hanami::Model**'s is thread safe during the runtime, but it isn't during the loading process.\nThe mapper compiles some code internally, so be sure to safely load it before your application starts.\n\n```ruby\nMutex.new.synchronize do\n  Hanami::Model.load!\nend\n```\n\n**This is not necessary when Hanami::Model is used within a Hanami application.**\n\n## Features\n\n### Timestamps\n\nIf an entity has the following accessors: `:created_at` and `:updated_at`, they will be automatically updated when the entity is persisted.\n\n```ruby\nrequire 'hanami/model'\nrequire 'hanami/model/sql'\n\nclass User < Hanami::Entity\nend\n\nclass UserRepository < Hanami::Repository\nend\n\nHanami::Model.configure do\n  adapter :sql, 'postgresql://localhost/bookshelf'\nend.load!\n\nrepository = UserRepository.new\n\nuser = repository.create(name: 'Luca')\n\nputs user.created_at.to_s # => \"2016-09-19 13:40:13 UTC\"\nputs user.updated_at.to_s # => \"2016-09-19 13:40:13 UTC\"\n\nsleep 3\nuser = repository.update(user.id, age: 34)\nputs user.created_at.to_s # => \"2016-09-19 13:40:13 UTC\"\nputs user.updated_at.to_s # => \"2016-09-19 13:40:16 UTC\"\n```\n\n## Configuration\n\n### Logging\n\nIn order to log database operations, you can configure a logger:\n\n```ruby\nHanami::Model.configure do\n  # ...\n  logger \"log/development.log\", level: :debug\nend\n```\n\nIt accepts the following arguments:\n\n  * `stream`: a Ruby StringIO object - it can be `$stdout` or a path to file (eg. `\"log/development.log\"`) - Defaults to `$stdout`\n  * `:level`: logging level - it can be: `:debug`, `:info`, `:warn`, `:error`, `:fatal`, `:unknown` - Defaults to `:debug`\n  * `:formatter`: logging formatter - it can be: `:default` or `:json` - Defaults to `:default`\n\n## Versioning\n\n__Hanami::Model__ uses [Semantic Versioning 2.0.0](http://semver.org)\n\n## Contributing\n\n1. Fork it ( https://github.com/hanami/model/fork )\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n\n## Copyright\n\nCopyright © 2014-2021 Luca Guidi – Released under MIT License\n\nThis project was formerly known as Lotus (`lotus-model`).\n"
  },
  {
    "path": "Rakefile",
    "content": "# frozen_string_literal: true\n\nrequire \"rake\"\nrequire \"bundler/gem_tasks\"\nrequire \"rspec/core/rake_task\"\nrequire \"hanami/devtools/rake_tasks\"\n\nnamespace :spec do\n  RSpec::Core::RakeTask.new(:unit) do |task|\n    task.pattern = FileList[\"spec/**/*_spec.rb\"]\n  end\nend\n\ntask default: \"spec:unit\"\n"
  },
  {
    "path": "hanami-model.gemspec",
    "content": "# frozen_string_literal: true\n\nlib = File.expand_path(\"../lib\", __FILE__)\n$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)\nrequire \"hanami/model/version\"\n\nGem::Specification.new do |spec|\n  spec.name          = \"hanami-model\"\n  spec.version       = Hanami::Model::VERSION\n  spec.authors       = [\"Luca Guidi\"]\n  spec.email         = [\"me@lucaguidi.com\"]\n  spec.summary       = \"A persistence layer for Hanami\"\n  spec.description   = \"A persistence framework with entities and repositories\"\n  spec.homepage      = \"http://hanamirb.org\"\n  spec.license       = \"MIT\"\n\n  spec.files         = `git ls-files -z -- lib/* CHANGELOG.md EXAMPLE.md LICENSE.md README.md hanami-model.gemspec`.split(\"\\x0\")\n  spec.executables   = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }\n  spec.test_files    = spec.files.grep(%r{^(test|spec|features)/})\n  spec.require_paths = [\"lib\"]\n  spec.required_ruby_version = \">= 2.3.0\", \"< 3\"\n\n  spec.add_runtime_dependency \"hanami-utils\",    \"~> 1.3\"\n  spec.add_runtime_dependency \"rom\",             \"~> 3.3\", \">= 3.3.3\"\n  spec.add_runtime_dependency \"rom-sql\",         \"~> 1.3\", \">= 1.3.5\"\n  spec.add_runtime_dependency \"rom-repository\",  \"~> 1.4\"\n  spec.add_runtime_dependency \"dry-types\",       \"~> 0.11.0\"\n  spec.add_runtime_dependency \"dry-logic\",       \"~> 0.4.2\", \"< 0.5\"\n  spec.add_runtime_dependency \"concurrent-ruby\", \"~> 1.0\"\n  spec.add_runtime_dependency \"bigdecimal\",      \"~> 1.4\"\n\n  spec.add_development_dependency \"bundler\", \">= 1.6\", \"< 3\"\n  spec.add_development_dependency \"rake\",  \"~> 12\"\n  spec.add_development_dependency \"rspec\", \"~> 3.7\"\n  spec.add_development_dependency \"rubocop\", \"0.81\" # rubocop 0.81+ removed support for Ruby 2.3\nend\n"
  },
  {
    "path": "lib/hanami/entity/schema.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"hanami/model/types\"\nrequire \"hanami/utils/hash\"\n\nmodule Hanami\n  class Entity\n    # Entity schema is a definition of a set of typed attributes.\n    #\n    # @since 0.7.0\n    # @api private\n    #\n    # @example SQL Automatic Setup\n    #  require 'hanami/model'\n    #\n    #   class Account < Hanami::Entity\n    #   end\n    #\n    #   account = Account.new(name: \"Acme Inc.\")\n    #   account.name # => \"Hanami\"\n    #\n    #   account = Account.new(foo: \"bar\")\n    #   account.foo # => NoMethodError\n    #\n    # @example Non-SQL Manual Setup\n    #   require 'hanami/model'\n    #\n    #   class Account < Hanami::Entity\n    #     attributes do\n    #       attribute :id,         Types::Int\n    #       attribute :name,       Types::String\n    #       attribute :codes,      Types::Array(Types::Int)\n    #       attribute :users,      Types::Array(User)\n    #       attribute :email,      Types::String.constrained(format: /@/)\n    #       attribute :created_at, Types::DateTime\n    #     end\n    #   end\n    #\n    #   account = Account.new(name: \"Acme Inc.\")\n    #   account.name # => \"Acme Inc.\"\n    #\n    #   account = Account.new(foo: \"bar\")\n    #   account.foo # => NoMethodError\n    #\n    # @example Schemaless Entity\n    #   require 'hanami/model'\n    #\n    #   class Account < Hanami::Entity\n    #   end\n    #\n    #   account = Account.new(name: \"Acme Inc.\")\n    #   account.name # => \"Acme Inc.\"\n    #\n    #   account = Account.new(foo: \"bar\")\n    #   account.foo # => \"bar\"\n    class Schema\n      # Schemaless entities logic\n      #\n      # @since 0.7.0\n      # @api private\n      class Schemaless\n        # @since 0.7.0\n        # @api private\n        def initialize\n          freeze\n        end\n\n        # @param attributes [#to_hash] the attributes hash\n        #\n        # @return [Hash]\n        #\n        # @since 0.7.0\n        # @api private\n        def call(attributes)\n          if attributes.nil?\n            {}\n          else\n            Utils::Hash.deep_symbolize(attributes.to_hash.dup)\n          end\n        end\n\n        # @since 0.7.0\n        # @api private\n        def attribute?(_name)\n          true\n        end\n      end\n\n      # Schema definition\n      #\n      # @since 0.7.0\n      # @api private\n      class Definition\n        # Schema DSL\n        #\n        # @since 0.7.0\n        class Dsl\n          # @since 1.1.0\n          # @api private\n          TYPES = %i[schema strict weak permissive strict_with_defaults symbolized].freeze\n\n          # @since 1.1.0\n          # @api private\n          DEFAULT_TYPE = TYPES.first\n\n          # @since 0.7.0\n          # @api private\n          def self.build(type, &blk)\n            type ||= DEFAULT_TYPE\n            raise Hanami::Model::Error.new(\"Unknown schema type: `#{type.inspect}'\") unless TYPES.include?(type)\n\n            attributes = new(&blk).to_h\n            [attributes, Hanami::Model::Types::Coercible::Hash.__send__(type, attributes)]\n          end\n\n          # @since 0.7.0\n          # @api private\n          def initialize(&blk)\n            @attributes = {}\n            instance_eval(&blk)\n          end\n\n          # Define an attribute\n          #\n          # @param name [Symbol] the attribute name\n          # @param type [Dry::Types::Definition] the attribute type\n          #\n          # @since 0.7.0\n          #\n          # @example\n          #   require 'hanami/model'\n          #\n          #   class Account < Hanami::Entity\n          #     attributes do\n          #       attribute :id,         Types::Int\n          #       attribute :name,       Types::String\n          #       attribute :codes,      Types::Array(Types::Int)\n          #       attribute :users,      Types::Array(User)\n          #       attribute :email,      Types::String.constrained(format: /@/)\n          #       attribute :created_at, Types::DateTime\n          #     end\n          #   end\n          #\n          #   account = Account.new(name: \"Acme Inc.\")\n          #   account.name # => \"Acme Inc.\"\n          #\n          #   account = Account.new(foo: \"bar\")\n          #   account.foo # => NoMethodError\n          def attribute(name, type)\n            @attributes[name] = type\n          end\n\n          # @since 0.7.0\n          # @api private\n          def to_h\n            @attributes\n          end\n        end\n\n        # Instantiate a new DSL instance for an entity\n        #\n        # @param blk [Proc] the block that defines the attributes\n        #\n        # @return [Hanami::Entity::Schema::Dsl] the DSL\n        #\n        # @since 0.7.0\n        # @api private\n        def initialize(type = nil, &blk)\n          raise LocalJumpError unless block_given?\n\n          @attributes, @schema = Dsl.build(type, &blk)\n          @attributes = Hash[@attributes.map { |k, _| [k, true] }]\n          freeze\n        end\n\n        # Process attributes\n        #\n        # @param attributes [#to_hash] the attributes hash\n        #\n        # @raise [TypeError] if the process fails\n        # @raise [ArgumentError] if data is missing, or unknown keys are given\n        #\n        # @since 0.7.0\n        # @api private\n        def call(attributes)\n          schema.call(attributes)\n        rescue Dry::Types::SchemaError => exception\n          raise TypeError.new(exception.message)\n        rescue Dry::Types::MissingKeyError, Dry::Types::UnknownKeysError => exception\n          raise ArgumentError.new(exception.message)\n        end\n\n        # Check if the attribute is known\n        #\n        # @param name [Symbol] the attribute name\n        #\n        # @return [TrueClass,FalseClass] the result of the check\n        #\n        # @since 0.7.0\n        # @api private\n        def attribute?(name)\n          attributes.key?(name)\n        end\n\n        private\n\n        # @since 0.7.0\n        # @api private\n        attr_reader :schema\n\n        # @since 0.7.0\n        # @api private\n        attr_reader :attributes\n      end\n\n      # Build a new instance of Schema with the attributes defined by the given block\n      #\n      # @param blk [Proc] the optional block that defines the attributes\n      #\n      # @return [Hanami::Entity::Schema] the schema\n      #\n      # @since 0.7.0\n      # @api private\n      def initialize(type = nil, &blk)\n        @schema = if block_given?\n                    Definition.new(type, &blk)\n                  else\n                    Schemaless.new\n                  end\n      end\n\n      # Process attributes\n      #\n      # @param attributes [#to_hash] the attributes hash\n      #\n      # @raise [TypeError] if the process fails\n      #\n      # @since 0.7.0\n      # @api private\n      def call(attributes)\n        Utils::Hash.deep_symbolize(\n          schema.call(attributes)\n        )\n      end\n\n      # @since 0.7.0\n      # @api private\n      alias_method :[], :call\n\n      # Check if the attribute is known\n      #\n      # @param name [Symbol] the attribute name\n      #\n      # @return [TrueClass,FalseClass] the result of the check\n      #\n      # @since 0.7.0\n      # @api private\n      def attribute?(name)\n        schema.attribute?(name)\n      end\n\n      protected\n\n      # @since 0.7.0\n      # @api private\n      attr_reader :schema\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/entity.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"hanami/model/types\"\n\nmodule Hanami\n  # An object that is defined by its identity.\n  # See \"Domain Driven Design\" by Eric Evans.\n  #\n  # An entity is the core of an application, where the part of the domain\n  # logic is implemented. It's a small, cohesive object that expresses coherent\n  # and meaningful behaviors.\n  #\n  # It deals with one and only one responsibility that is pertinent to the\n  # domain of the application, without caring about details such as persistence\n  # or validations.\n  #\n  # This simplicity of design allows developers to focus on behaviors, or\n  # message passing if you will, which is the quintessence of Object Oriented\n  # Programming.\n  #\n  # @example With Hanami::Entity\n  #   require 'hanami/model'\n  #\n  #   class Person < Hanami::Entity\n  #   end\n  #\n  # If we expand the code above in **pure Ruby**, it would be:\n  #\n  # @example Pure Ruby\n  #   class Person\n  #     attr_accessor :id, :name, :age\n  #\n  #     def initialize(attributes = {})\n  #       @id, @name, @age = attributes.values_at(:id, :name, :age)\n  #     end\n  #   end\n  #\n  # **Hanami::Model** ships `Hanami::Entity` for developers' convenience.\n  #\n  # **Hanami::Model** depends on a narrow and well-defined interface for an\n  # Entity - `#id`, `#id=`, `#initialize(attributes={})`.If your object\n  # implements that interface then that object can be used as an Entity in the\n  # **Hanami::Model** framework.\n  #\n  # However, we suggest to implement this interface by including\n  # `Hanami::Entity`, in case that future versions of the framework will expand\n  # it.\n  #\n  # See Dependency Inversion Principle for more on interfaces.\n  #\n  # @since 0.1.0\n  #\n  # @see Hanami::Repository\n  class Entity\n    require \"hanami/entity/schema\"\n\n    # Syntactic shortcut to reference types in custom schema DSL\n    #\n    # @since 0.7.0\n    module Types\n      include Hanami::Model::Types\n    end\n\n    # Class level interface\n    #\n    # @since 0.7.0\n    # @api private\n    module ClassMethods\n      # Define manual entity schema\n      #\n      # With a SQL database this setup happens automatically and you SHOULD NOT\n      # use this DSL. You should use only when you want to customize the automatic\n      # setup.\n      #\n      # If you're working with an entity that isn't \"backed\" by a SQL table or\n      # with a schema-less database, you may want to manually setup a set of\n      # attributes via this DSL. If you don't do any setup, the entity accepts all\n      # the given attributes.\n      #\n      # @param type [Symbol] the type of schema to build\n      # @param blk [Proc] the block that defines the attributes\n      #\n      # @since 0.7.0\n      #\n      # @see Hanami::Entity\n      def attributes(type = nil, &blk)\n        self.schema = Schema.new(type, &blk)\n        @attributes = true\n      end\n\n      # Assign a schema\n      #\n      # @param value [Hanami::Entity::Schema] the schema\n      #\n      # @since 0.7.0\n      # @api private\n      def schema=(value)\n        return if defined?(@attributes)\n\n        @schema = value\n      end\n\n      # @since 0.7.0\n      # @api private\n      attr_reader :schema\n    end\n\n    # @since 0.7.0\n    # @api private\n    def self.inherited(klass)\n      klass.class_eval do\n        @schema = Schema.new\n        extend  ClassMethods\n      end\n    end\n\n    # Instantiate a new entity\n    #\n    # @param attributes [Hash,#to_h,NilClass] data to initialize the entity\n    #\n    # @return [Hanami::Entity] the new entity instance\n    #\n    # @raise [TypeError] if the given attributes are invalid\n    #\n    # @since 0.1.0\n    def initialize(attributes = nil)\n      @attributes = self.class.schema[attributes]\n      freeze\n    end\n\n    # Entity ID\n    #\n    # @return [Object,NilClass] the ID, if present\n    #\n    # @since 0.7.0\n    def id\n      attributes.fetch(:id, nil)\n    end\n\n    # Handle dynamic accessors\n    #\n    # If internal attributes set has the requested key, it returns the linked\n    # value, otherwise it raises a <tt>NoMethodError</tt>\n    #\n    # @since 0.7.0\n    def method_missing(method_name, *)\n      attribute?(method_name) or super\n      attributes.fetch(method_name, nil)\n    end\n\n    # Implement generic equality for entities\n    #\n    # Two entities are equal if they are instances of the same class and they\n    # have the same id.\n    #\n    # @param other [Object] the object of comparison\n    #\n    # @return [FalseClass,TrueClass] the result of the check\n    #\n    # @since 0.1.0\n    def ==(other)\n      self.class == other.class &&\n        id == other.id\n    end\n\n    # Implement predictable hashing for hash equality\n    #\n    # @return [Integer] the object hash\n    #\n    # @since 0.7.0\n    def hash\n      [self.class, id].hash\n    end\n\n    # Freeze the entity\n    #\n    # @since 0.7.0\n    def freeze\n      attributes.freeze\n      super\n    end\n\n    # Serialize entity to a Hash\n    #\n    # @return [Hash] the result of serialization\n    #\n    # @since 0.1.0\n    def to_h\n      Utils::Hash.deep_dup(attributes)\n    end\n\n    # @since 0.7.0\n    alias_method :to_hash, :to_h\n\n    protected\n\n    # Check if the attribute is allowed to be read\n    #\n    # @since 0.7.0\n    # @api private\n    def attribute?(name)\n      self.class.schema.attribute?(name)\n    end\n\n    private\n\n    # @since 0.1.0\n    # @api private\n    attr_reader :attributes\n\n    # @since 0.7.0\n    # @api private\n    def respond_to_missing?(name, _include_all)\n      attribute?(name)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/association.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rom-sql\"\nrequire \"hanami/model/associations/belongs_to\"\nrequire \"hanami/model/associations/has_many\"\nrequire \"hanami/model/associations/has_one\"\nrequire \"hanami/model/associations/many_to_many\"\n\nmodule Hanami\n  module Model\n    # Association factory\n    #\n    # @since 0.7.0\n    # @api private\n    class Association\n      # Instantiate an association\n      #\n      # @since 0.7.0\n      # @api private\n      def self.build(repository, target, subject)\n        lookup(repository.root.associations[target])\n          .new(repository, repository.root.name.to_sym, target, subject)\n      end\n\n      # Translate ROM SQL associations into Hanami::Model associations\n      #\n      # @since 0.7.0\n      # @api private\n      def self.lookup(association)\n        case association\n        when ROM::SQL::Association::ManyToMany\n          Associations::ManyToMany\n        when ROM::SQL::Association::OneToOne\n          Associations::HasOne\n        when ROM::SQL::Association::OneToMany\n          Associations::HasMany\n        when ROM::SQL::Association::ManyToOne\n          Associations::BelongsTo\n        else\n          raise \"Unsupported association: #{association}\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/associations/belongs_to.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"hanami/model/types\"\n\nmodule Hanami\n  module Model\n    module Associations\n      # Many-To-One association\n      #\n      # @since 1.1.0\n      # @api private\n      class BelongsTo\n        # @since 1.1.0\n        # @api private\n        def self.schema_type(entity)\n          Sql::Types::Schema::AssociationType.new(entity)\n        end\n\n        # @since 1.1.0\n        # @api private\n        attr_reader :repository\n\n        # @since 1.1.0\n        # @api private\n        attr_reader :source\n\n        # @since 1.1.0\n        # @api private\n        attr_reader :target\n\n        # @since 1.1.0\n        # @api private\n        attr_reader :subject\n\n        # @since 1.1.0\n        # @api private\n        attr_reader :scope\n\n        # @since 1.1.0\n        # @api private\n        def initialize(repository, source, target, subject, scope = nil)\n          @repository = repository\n          @source     = source\n          @target     = target\n          @subject    = subject.to_hash unless subject.nil?\n          @scope      = scope || _build_scope\n          freeze\n        end\n\n        # @since 1.1.0\n        # @api private\n        def one\n          scope.one\n        end\n\n        private\n\n        # @since 1.1.0\n        # @api private\n        def container\n          repository.container\n        end\n\n        # @since 1.1.0\n        # @api private\n        def primary_key\n          association_keys.first\n        end\n\n        # @since 1.1.0\n        # @api private\n        def relation(name)\n          repository.relations[Hanami::Utils::String.pluralize(name)]\n        end\n\n        # @since 1.1.0\n        # @api private\n        def foreign_key\n          association_keys.last\n        end\n\n        # Returns primary key and foreign key\n        #\n        # @since 1.1.0\n        # @api private\n        def association_keys\n          association\n            .__send__(:join_key_map, container.relations)\n        end\n\n        # Return the ROM::Associations for the source relation\n        #\n        # @since 1.1.9\n        # @api private\n        def association\n          relation(source).associations[target]\n        end\n\n        # @since 1.1.0\n        # @api private\n        def _build_scope\n          result = relation(association.target.to_sym)\n          result = result.where(foreign_key => subject.fetch(primary_key)) unless subject.nil?\n          result.as(Model::MappedRelation.mapper_name)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/associations/dsl.rb",
    "content": "# frozen_string_literal: true\n\nmodule Hanami\n  module Model\n    module Associations\n      # Auto-infer relations linked to repository's associations\n      #\n      # @since 0.7.0\n      # @api private\n      #\n      class Dsl\n        # @since 0.7.0\n        # @api private\n        def initialize(repository, &blk)\n          @repository = repository\n          instance_eval(&blk)\n        end\n\n        # @since 0.7.0\n        # @api private\n        def has_many(relation, **args)\n          @repository.__send__(:relations, relation)\n          @repository.__send__(:relations, args[:through]) if args[:through]\n        end\n\n        # @since 1.1.0\n        # @api private\n        def has_one(relation, *)\n          @repository.__send__(:relations, Hanami::Utils::String.pluralize(relation).to_sym)\n        end\n\n        # @since 1.1.0\n        # @api private\n        def belongs_to(relation, *)\n          @repository.__send__(:relations, Hanami::Utils::String.pluralize(relation).to_sym)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/associations/has_many.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"hanami/model/types\"\n\nmodule Hanami\n  module Model\n    module Associations\n      # One-To-Many association\n      #\n      # @since 0.7.0\n      # @api private\n      class HasMany\n        # @since 0.7.0\n        # @api private\n        def self.schema_type(entity)\n          type = Sql::Types::Schema::AssociationType.new(entity)\n          Types::Strict::Array.member(type)\n        end\n\n        # @since 0.7.0\n        # @api private\n        attr_reader :repository\n\n        # @since 0.7.0\n        # @api private\n        attr_reader :source\n\n        # @since 0.7.0\n        # @api private\n        attr_reader :target\n\n        # @since 0.7.0\n        # @api private\n        attr_reader :subject\n\n        # @since 0.7.0\n        # @api private\n        attr_reader :scope\n\n        # @since 0.7.0\n        # @api private\n        def initialize(repository, source, target, subject, scope = nil)\n          @repository = repository\n          @source     = source\n          @target     = target\n          @subject    = subject.to_hash unless subject.nil?\n          @scope      = scope || _build_scope\n          freeze\n        end\n\n        # @since 0.7.0\n        # @api private\n        def create(data)\n          entity.new(command(:create, aggregate(target), mapper: nil, use: [:timestamps])\n            .call(serialize(data)))\n        rescue => exception\n          raise Hanami::Model::Error.for(exception)\n        end\n\n        # @since 0.7.0\n        # @api private\n        def add(data)\n          command(:create, relation(target), use: [:timestamps])\n            .call(associate(serialize(data)))\n        rescue => exception\n          raise Hanami::Model::Error.for(exception)\n        end\n\n        # @since 0.7.0\n        # @api private\n        def remove(id)\n          command(:update, relation(target), use: [:timestamps])\n            .by_pk(id)\n            .call(unassociate)\n        end\n\n        # @since 0.7.0\n        # @api private\n        def delete\n          scope.delete\n        end\n\n        # @since 0.7.0\n        # @api private\n        def each(&blk)\n          scope.each(&blk)\n        end\n\n        # @since 0.7.0\n        # @api private\n        def map(&blk)\n          to_a.map(&blk)\n        end\n\n        # @since 0.7.0\n        # @api private\n        def to_a\n          scope.to_a\n        end\n\n        # @since 0.7.0\n        # @api private\n        def where(condition)\n          __new__(scope.where(condition))\n        end\n\n        # @since 0.7.0\n        # @api private\n        def count\n          scope.count\n        end\n\n        private\n\n        # @since 0.7.0\n        # @api private\n        def command(target, relation, options = {})\n          repository.command(target, relation, options)\n        end\n\n        # @since 0.7.0\n        # @api private\n        def entity\n          repository.class.entity\n        end\n\n        # @since 0.7.0\n        # @api private\n        def relation(name)\n          repository.relations[name]\n        end\n\n        # @since 0.7.0\n        # @api private\n        def aggregate(name)\n          repository.aggregate(name)\n        end\n\n        # @since 0.7.0\n        # @api private\n        def association(name)\n          relation(target).associations[name]\n        end\n\n        # @since 0.7.0\n        # @api private\n        def associate(data)\n          relation(source)\n            .associations[target]\n            .associate(container.relations, data, subject)\n        end\n\n        # @since 0.7.0\n        # @api private\n        def unassociate\n          {foreign_key => nil}\n        end\n\n        # @since 0.7.0\n        # @api private\n        def container\n          repository.container\n        end\n\n        # @since 0.7.0\n        # @api private\n        def primary_key\n          association_keys.first\n        end\n\n        # @since 0.7.0\n        # @api private\n        def foreign_key\n          association_keys.last\n        end\n\n        # Returns primary key and foreign key\n        #\n        # @since 0.7.0\n        # @api private\n        def association_keys\n          target_association\n            .__send__(:join_key_map, container.relations)\n        end\n\n        # Returns the targeted association for a given source\n        #\n        # @since 0.7.0\n        # @api private\n        def target_association\n          relation(source).associations[target]\n        end\n\n        # @since 0.7.0\n        # @api private\n        def _build_scope\n          result = relation(target_association.target.to_sym)\n          result = result.where(foreign_key => subject.fetch(primary_key)) unless subject.nil?\n          result.as(Model::MappedRelation.mapper_name)\n        end\n\n        # @since 0.7.0\n        # @api private\n        def __new__(new_scope)\n          self.class.new(repository, source, target, subject, new_scope)\n        end\n\n        def serialize(data)\n          Utils::Hash.deep_serialize(data)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/associations/has_one.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"hanami/utils/hash\"\n\nmodule Hanami\n  module Model\n    module Associations\n      # Many-To-One association\n      #\n      # @since 1.1.0\n      # @api private\n      class HasOne\n        # @since 1.1.0\n        # @api private\n        def self.schema_type(entity)\n          Sql::Types::Schema::AssociationType.new(entity)\n        end\n        #\n        # @since 1.1.0\n        # @api private\n        attr_reader :repository\n\n        # @since 1.1.0\n        # @api private\n        attr_reader :source\n\n        # @since 1.1.0\n        # @api private\n        attr_reader :target\n\n        # @since 1.1.0\n        # @api private\n        attr_reader :subject\n\n        # @since 1.1.0\n        # @api private\n        attr_reader :scope\n\n        # @since 1.1.0\n        # @api private\n        def initialize(repository, source, target, subject, scope = nil)\n          @repository = repository\n          @source     = source\n          @target     = target\n          @subject    = subject.to_hash unless subject.nil?\n          @scope      = scope || _build_scope\n          freeze\n        end\n\n        def one\n          scope.one\n        end\n\n        def create(data)\n          entity.new(\n            command(:create, aggregate(target), mapper: nil).call(serialize(data))\n          )\n        rescue => exception\n          raise Hanami::Model::Error.for(exception)\n        end\n\n        def add(data)\n          command(:create, relation(target), mapper: nil).call(associate(serialize(data)))\n        rescue => exception\n          raise Hanami::Model::Error.for(exception)\n        end\n\n        def update(data)\n          command(:update, relation(target), mapper: nil)\n            .by_pk(\n              one.public_send(relation(target).primary_key)\n            ).call(serialize(data))\n        rescue => exception\n          raise Hanami::Model::Error.for(exception)\n        end\n\n        def delete\n          scope.delete\n        end\n        alias_method :remove, :delete\n\n        def replace(data)\n          repository.transaction do\n            delete\n            add(serialize(data))\n          end\n        end\n\n        private\n\n        # @since 1.1.0\n        # @api private\n        def entity\n          repository.class.entity\n        end\n\n        # @since 1.1.0\n        # @api private\n        def aggregate(name)\n          repository.aggregate(name)\n        end\n\n        # @since 1.1.0\n        # @api private\n        def command(target, relation, options = {})\n          repository.command(target, relation, options)\n        end\n\n        # @since 1.1.0\n        # @api private\n        def relation(name)\n          repository.relations[Hanami::Utils::String.pluralize(name)]\n        end\n\n        # @since 1.1.0\n        # @api private\n        def container\n          repository.container\n        end\n\n        # @since 1.1.0\n        # @api private\n        def primary_key\n          association_keys.first\n        end\n\n        # @since 1.1.0\n        # @api private\n        def foreign_key\n          association_keys.last\n        end\n\n        # @since 1.1.0\n        # @api private\n        def associate(data)\n          relation(source)\n            .associations[target]\n            .associate(container.relations, data, subject)\n        end\n\n        # Returns primary key and foreign key\n        #\n        # @since 1.1.0\n        # @api private\n        def association_keys\n          relation(source)\n            .associations[target]\n            .__send__(:join_key_map, container.relations)\n        end\n\n        # @since 1.1.0\n        # @api private\n        def _build_scope\n          result = relation(target)\n          result = result.where(foreign_key => subject.fetch(primary_key)) unless subject.nil?\n          result.as(Model::MappedRelation.mapper_name)\n        end\n\n        # @since 1.1.0\n        # @api private\n        def serialize(data)\n          Utils::Hash.deep_serialize(data)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/associations/many_to_many.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"hanami/utils/hash\"\n\nmodule Hanami\n  module Model\n    module Associations\n      # Many-To-Many association\n      #\n      # @since 0.7.0\n      # @api private\n      class ManyToMany\n        # @since 0.7.0\n        # @api private\n        def self.schema_type(entity)\n          type = Sql::Types::Schema::AssociationType.new(entity)\n          Types::Strict::Array.member(type)\n        end\n\n        # @since 1.1.0\n        # @api private\n        attr_reader :repository\n\n        # @since 1.1.0\n        # @api private\n        attr_reader :source\n\n        # @since 1.1.0\n        # @api private\n        attr_reader :target\n\n        # @since 1.1.0\n        # @api private\n        attr_reader :subject\n\n        # @since 1.1.0\n        # @api private\n        attr_reader :scope\n\n        # @since 1.1.0\n        # @api private\n        attr_reader :through\n\n        def initialize(repository, source, target, subject, scope = nil)\n          @repository = repository\n          @source     = source\n          @target     = target\n          @subject    = subject.to_hash unless subject.nil?\n          @through    = relation(source).associations[target].through.to_sym\n          @scope      = scope || _build_scope\n          freeze\n        end\n\n        def to_a\n          scope.to_a\n        end\n\n        def map(&blk)\n          to_a.map(&blk)\n        end\n\n        def each(&blk)\n          scope.each(&blk)\n        end\n\n        def count\n          scope.count\n        end\n\n        def where(condition)\n          __new__(scope.where(condition))\n        end\n\n        # Return the association table object. Would need an aditional query to return the entity\n        #\n        # @since 1.1.0\n        # @api private\n        def add(*data)\n          command(:create, relation(through), use: [:timestamps])\n            .call(associate(serialize(data)))\n        rescue => exception\n          raise Hanami::Model::Error.for(exception)\n        end\n\n        # @since 1.1.0\n        # @api private\n        def delete\n          relation(through).where(source_foreign_key => subject.fetch(source_primary_key)).delete\n        end\n\n        # @since 1.1.0\n        # @api private\n        def remove(target_id)\n          association_record = relation(through)\n            .where(target_foreign_key => target_id, source_foreign_key => subject.fetch(source_primary_key))\n            .one\n\n          return if association_record.nil?\n\n          ar_id = association_record.public_send relation(through).primary_key\n          command(:delete, relation(through)).by_pk(ar_id).call\n        end\n\n        private\n\n        # @since 1.1.0\n        # @api private\n        def container\n          repository.container\n        end\n\n        # @since 1.1.0\n        # @api private\n        def relation(name)\n          repository.relations[name]\n        end\n\n        # @since 1.1.0\n        # @api private\n        def command(target, relation, options = {})\n          repository.command(target, relation, options)\n        end\n\n        # @since 1.1.0\n        # @api private\n        def associate(data)\n          relation(target)\n            .associations[source]\n            .associate(container.relations, data, subject)\n        end\n\n        # @since 1.1.0\n        # @api private\n        def source_primary_key\n          association_keys[0].first\n        end\n\n        # @since 1.1.0\n        # @api private\n        def source_foreign_key\n          association_keys[0].last\n        end\n\n        # @since 1.1.0\n        # @api private\n        def association_keys\n          relation(source)\n            .associations[target]\n            .__send__(:join_key_map, container.relations)\n        end\n\n        # @since 1.1.0\n        # @api private\n        def target_foreign_key\n          association_keys[1].first\n        end\n\n        # @since 1.1.0\n        # @api private\n        def target_primary_key\n          association_keys[1].last\n        end\n\n        # Return the ROM::Associations for the source relation\n        #\n        # @since 1.1.0\n        # @api private\n        def association\n          relation(source).associations[target]\n        end\n\n        # @since 1.1.0\n        #\n        # @api private\n        def _build_scope\n          result = relation(association.target.to_sym).qualified\n          unless subject.nil?\n            result = result\n              .join(through, target_foreign_key => target_primary_key)\n              .where(source_foreign_key => subject.fetch(source_primary_key))\n          end\n          result.as(Model::MappedRelation.mapper_name)\n        end\n\n        # @since 1.1.0\n        # @api private\n        def __new__(new_scope)\n          self.class.new(repository, source, target, subject, new_scope)\n        end\n\n        # @since 1.1.0\n        # @api private\n        def serialize(data)\n          data.map do |d|\n            Utils::Hash.deep_serialize(d)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/configuration.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rom/configuration\"\n\nmodule Hanami\n  module Model\n    # Configuration for the framework, models and adapters.\n    #\n    # Hanami::Model has its own global configuration that can be manipulated\n    # via `Hanami::Model.configure`.\n    #\n    # @since 0.2.0\n    class Configuration\n      # @since 0.7.0\n      # @api private\n      attr_reader :mappings\n\n      # @since 0.7.0\n      # @api private\n      attr_reader :entities\n\n      # @since 1.0.0\n      # @api private\n      attr_reader :logger\n\n      # @since 1.0.0\n      # @api private\n      attr_reader :migrations_logger\n\n      # @since 0.2.0\n      # @api private\n      def initialize(configurator)\n        @backend = configurator.backend\n        @url = configurator.url\n        @migrations = configurator._migrations\n        @schema = configurator._schema\n        @gateway_config = configurator._gateway\n        @logger = configurator._logger\n        @migrations_logger = configurator.migrations_logger\n        @mappings = {}\n        @entities = {}\n      end\n\n      # NOTE: This must be changed when we want to support several adapters at the time\n      #\n      # @since 0.7.0\n      # @api private\n      attr_reader :url\n\n      # NOTE: This must be changed when we want to support several adapters at the time\n      #\n      # @raise [Hanami::Model::UnknownDatabaseAdapterError] if `url` is blank,\n      #   or it uses an unknown adapter.\n      #\n      # @since 0.7.0\n      # @api private\n      def connection\n        gateway.connection\n      end\n\n      # NOTE: This must be changed when we want to support several adapters at the time\n      #\n      # @raise [Hanami::Model::UnknownDatabaseAdapterError] if `url` is blank,\n      #   or it uses an unknown adapter.\n      #\n      # @since 0.7.0\n      # @api private\n      def gateway\n        gateways[:default]\n      end\n\n      # Root directory\n      #\n      # @since 0.4.0\n      # @api private\n      def root\n        Hanami.respond_to?(:root) ? Hanami.root : Pathname.pwd\n      end\n\n      # Migrations directory\n      #\n      # @since 0.4.0\n      def migrations\n        (@migrations.nil? ? root : root.join(@migrations)).realpath\n      end\n\n      # Path for schema dump file\n      #\n      # @since 0.4.0\n      def schema\n        @schema.nil? ? root : root.join(@schema)\n      end\n\n      # @since 0.7.0\n      # @api private\n      def define_mappings(root, &blk)\n        @mappings[root] = Mapping.new(&blk)\n      end\n\n      # @since 0.7.0\n      # @api private\n      def register_entity(plural, singular, klass)\n        @entities[plural]   = klass\n        @entities[singular] = klass\n      end\n\n      # @since 0.7.0\n      # @api private\n      def define_entities_mappings(container, repositories)\n        return unless defined?(Sql::Entity::Schema)\n\n        repositories.each do |r|\n          relation = r.relation\n          entity   = r.entity\n\n          entity.schema = Sql::Entity::Schema.new(entities, container.relations[relation], mappings.fetch(relation))\n        end\n      end\n\n      # @since 1.0.0\n      # @api private\n      def configure_gateway\n        @gateway_config&.call(gateway)\n      end\n\n      # @since 1.0.0\n      # @api private\n      def logger=(value)\n        return if value.nil?\n\n        gateway.use_logger(@logger = value)\n      end\n\n      # @raise [Hanami::Model::UnknownDatabaseAdapterError] if `url` is blank,\n      #   or it uses an unknown adapter.\n      #\n      # @since 1.0.0\n      # @api private\n      def rom\n        @rom ||= ROM::Configuration.new(@backend, @url, infer_relations: false)\n      rescue => exception\n        raise UnknownDatabaseAdapterError.new(@url) if exception.message =~ /adapters/\n\n        raise exception\n      end\n\n      # @raise [Hanami::Model::UnknownDatabaseAdapterError] if `url` is blank,\n      #   or it uses an unknown adapter.\n      #\n      # @since 1.0.0\n      # @api private\n      def load!(repositories, &blk)\n        rom.setup.auto_registration(config.directory.to_s) unless config.directory.nil?\n        rom.instance_eval(&blk)                            if     block_given?\n        configure_gateway\n        repositories.each(&:load!)\n        self.logger = logger\n\n        container = ROM.container(rom)\n        define_entities_mappings(container, repositories)\n        container\n      rescue => exception\n        raise Hanami::Model::Error.for(exception)\n      end\n\n      # @since 1.0.0\n      # @api private\n      def method_missing(method_name, *args, &blk)\n        if rom.respond_to?(method_name)\n          rom.__send__(method_name, *args, &blk)\n        else\n          super\n        end\n      end\n\n      # @since 1.1.0\n      # @api private\n      def respond_to_missing?(method_name, include_all)\n        rom.respond_to?(method_name, include_all)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/configurator.rb",
    "content": "# frozen_string_literal: true\n\nmodule Hanami\n  module Model\n    # Configuration DSL\n    #\n    # @since 0.7.0\n    # @api private\n    class Configurator\n      # @since 0.7.0\n      # @api private\n      attr_reader :backend\n\n      # @since 0.7.0\n      # @api private\n      attr_reader :url\n\n      # @since 0.7.0\n      # @api private\n      attr_reader :directory\n\n      # @since 0.7.0\n      # @api private\n      attr_reader :_migrations\n\n      # @since 0.7.0\n      # @api private\n      attr_reader :_schema\n\n      # @since 1.0.0\n      # @api private\n      attr_reader :_logger\n\n      # @since 1.0.0\n      # @api private\n      attr_reader :_gateway\n\n      # @since 0.7.0\n      # @api private\n      def self.build(&block)\n        new.tap { |config| config.instance_eval(&block) }\n      end\n\n      # @since 1.0.0\n      # @api private\n      def migrations_logger(stream = $stdout)\n        require \"hanami/model/migrator/logger\"\n        @migrations_logger ||= Hanami::Model::Migrator::Logger.new(stream)\n      end\n\n      private\n\n      # @since 0.7.0\n      # @api private\n      def adapter(backend, url)\n        @backend = backend\n        @url = url\n      end\n\n      # @since 0.7.0\n      # @api private\n      def path(path)\n        @directory = path\n      end\n\n      # @since 0.7.0\n      # @api private\n      def migrations(path)\n        @_migrations = path\n      end\n\n      # @since 0.7.0\n      # @api private\n      def schema(path)\n        @_schema = path\n      end\n\n      # @since 1.0.0\n      # @api private\n      def logger(stream, options = {})\n        require \"hanami/logger\"\n\n        opts = options.merge(stream: stream)\n        @_logger = Hanami::Logger.new(\"hanami.model\", **opts)\n      end\n\n      # @since 1.0.0\n      # @api private\n      def gateway(&blk)\n        @_gateway = blk\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/entity_name.rb",
    "content": "# frozen_string_literal: true\n\nmodule Hanami\n  module Model\n    # Conventional name for entities.\n    #\n    # Given a repository named <tt>SourceFileRepository</tt>, the associated\n    # entity will be <tt>SourceFile</tt>.\n    #\n    # @since 0.7.0\n    # @api private\n    class EntityName\n      # @since 0.7.0\n      # @api private\n      SUFFIX = /Repository\\z/.freeze\n\n      # @param name [Class,String] the class or its name\n      # @return [String] the entity name\n      #\n      # @since 0.7.0\n      # @api private\n      def initialize(name)\n        @name = name.sub(SUFFIX, \"\")\n      end\n\n      # @since 0.7.0\n      # @api private\n      def underscore\n        Utils::String.underscore(@name).to_sym\n      end\n\n      # @since 0.7.0\n      # @api private\n      def to_s\n        @name\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/error.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"concurrent\"\n\nmodule Hanami\n  module Model\n    # Default Error class\n    #\n    # @since 0.5.1\n    class Error < ::StandardError\n      # @api private\n      # @since 0.7.0\n      @__mapping__ = Concurrent::Map.new\n\n      # @api private\n      # @since 0.7.0\n      def self.for(exception)\n        mapping.fetch(exception.class, self).new(exception)\n      end\n\n      # @api private\n      # @since 0.7.0\n      def self.register(external, internal)\n        mapping.put_if_absent(external, internal)\n      end\n\n      # @api private\n      # @since 0.7.0\n      def self.mapping\n        @__mapping__\n      end\n    end\n\n    # Generic database error\n    #\n    # @since 0.7.0\n    class DatabaseError < Error\n    end\n\n    # Error for invalid raw command syntax\n    #\n    # @since 0.5.0\n    class InvalidCommandError < Error\n      # @since 0.5.0\n      # @api private\n      def initialize(message = \"Invalid command\")\n        super\n      end\n    end\n\n    # Error for Constraint Violation\n    #\n    # @since 0.7.0\n    class ConstraintViolationError < Error\n      # @since 0.7.0\n      # @api private\n      def initialize(message = \"Constraint has been violated\")\n        super\n      end\n    end\n\n    # Error for Unique Constraint Violation\n    #\n    # @since 0.6.1\n    class UniqueConstraintViolationError < ConstraintViolationError\n      # @since 0.6.1\n      # @api private\n      def initialize(message = \"Unique constraint has been violated\")\n        super\n      end\n    end\n\n    # Error for Foreign Key Constraint Violation\n    #\n    # @since 0.6.1\n    class ForeignKeyConstraintViolationError < ConstraintViolationError\n      # @since 0.6.1\n      # @api private\n      def initialize(message = \"Foreign key constraint has been violated\")\n        super\n      end\n    end\n\n    # Error for Not Null Constraint Violation\n    #\n    # @since 0.6.1\n    class NotNullConstraintViolationError < ConstraintViolationError\n      # @since 0.6.1\n      # @api private\n      def initialize(message = \"NOT NULL constraint has been violated\")\n        super\n      end\n    end\n\n    # Error for Check Constraint Violation raised by Sequel\n    #\n    # @since 0.6.1\n    class CheckConstraintViolationError < ConstraintViolationError\n      # @since 0.6.1\n      # @api private\n      def initialize(message = \"Check constraint has been violated\")\n        super\n      end\n    end\n\n    # Unknown database type error for repository auto-mapping\n    #\n    # @since 1.0.0\n    class UnknownDatabaseTypeError < Error\n    end\n\n    # Unknown primary key error\n    #\n    # @since 1.0.0\n    class MissingPrimaryKeyError < Error\n    end\n\n    # Unknown attribute error\n    #\n    # @since 1.2.0\n    class UnknownAttributeError < Error\n    end\n\n    # Unknown database adapter error\n    #\n    # @since 1.2.1\n    class UnknownDatabaseAdapterError < Error\n      def initialize(url)\n        super(\"Unknown database adapter for URL: #{url.inspect}. Please check your database configuration (hint: ENV['DATABASE_URL']).\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/mapped_relation.rb",
    "content": "# frozen_string_literal: true\n\nmodule Hanami\n  module Model\n    # Mapped proxy for ROM relations.\n    #\n    # It eliminates the need to use #as for repository queries\n    #\n    # @since 1.0.0\n    # @api private\n    class MappedRelation < SimpleDelegator\n      # Mapper name.\n      #\n      # With ROM mapping there is a link between the entity class and a generic\n      # reference for it. Example: <tt>BookRepository</tt> references <tt>Book</tt>\n      # as <tt>:entity</tt>.\n      #\n      # @since 1.0.0\n      # @api private\n      MAPPER_NAME = :entity\n\n      # @since 1.0.0\n      # @api private\n      def self.mapper_name\n        MAPPER_NAME\n      end\n\n      # @since 1.0.0\n      # @api private\n      def initialize(relation)\n        @relation = relation\n        super(relation.as(self.class.mapper_name))\n      end\n\n      # Access low level relation's attribute\n      #\n      # @param attribute [Symbol] the attribute name\n      #\n      # @return [ROM::SQL::Attribute] the attribute\n      #\n      # @raise [Hanami::Model::UnknownAttributeError] if the attribute cannot be found\n      #\n      # @since 1.2.0\n      #\n      # @example\n      #   class UserRepository < Hanami::Repository\n      #     def by_matching_name(name)\n      #       users\n      #         .where(users[:name].ilike(name))\n      #         .map_to(User)\n      #         .to_a\n      #     end\n      #   end\n      def [](attribute)\n        @relation[attribute]\n      rescue KeyError => exception\n        raise UnknownAttributeError.new(exception.message)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/mapping.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"transproc/all\"\n\nmodule Hanami\n  module Model\n    # Mapping\n    #\n    # @since 0.1.0\n    # @api private\n    class Mapping\n      extend Transproc::Registry\n\n      import Transproc::HashTransformations\n\n      # @since 0.1.0\n      # @api private\n      def initialize(&blk)\n        @attributes   = {}\n        @r_attributes = {}\n        instance_eval(&blk)\n        @processor = @attributes.empty? ? ::Hash : t(:rename_keys, @attributes)\n      end\n\n      # @api private\n      def t(name, *args)\n        self.class[name, *args]\n      end\n\n      # @api private\n      def model(entity)\n      end\n\n      # @api private\n      def register_as(name)\n      end\n\n      # @api private\n      def attribute(name, options)\n        from = options.fetch(:from, name)\n\n        @attributes[name]   = from\n        @r_attributes[from] = name\n      end\n\n      # @api private\n      def process(input)\n        @processor[input]\n      end\n\n      # @api private\n      def reverse?\n        @r_attributes.any?\n      end\n\n      # @api private\n      def translate(attribute)\n        @r_attributes.fetch(attribute)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/migration.rb",
    "content": "# frozen_string_literal: true\n\nmodule Hanami\n  module Model\n    # Database migration\n    #\n    # @since 0.7.0\n    # @api private\n    class Migration\n      # @since 0.7.0\n      # @api private\n      attr_reader :gateway\n\n      # @since 0.7.0\n      # @api private\n      attr_reader :migration\n\n      # @since 0.7.0\n      # @api private\n      def initialize(gateway, &block)\n        @gateway = gateway\n        @migration = gateway.migration(&block)\n        freeze\n      end\n\n      # @since 0.7.0\n      # @api private\n      def run(direction = :up)\n        migration.apply(gateway.connection, direction)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/migrator/adapter.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"uri\"\nrequire \"shellwords\"\nrequire \"open3\"\n\nmodule Hanami\n  module Model\n    class Migrator\n      # Migrator base adapter\n      #\n      # @since 0.4.0\n      # @api private\n      class Adapter\n        # Migrations table to store migrations metadata.\n        #\n        # @since 0.4.0\n        # @api private\n        MIGRATIONS_TABLE = :schema_migrations\n\n        # Migrations table version column\n        #\n        # @since 0.4.0\n        # @api private\n        MIGRATIONS_TABLE_VERSION_COLUMN = :filename\n\n        # Loads and returns a specific adapter for the given connection.\n        #\n        # @since 0.4.0\n        # @api private\n        def self.for(configuration)\n          connection = Connection.new(configuration)\n\n          case connection.database_type\n          when :sqlite\n            require \"hanami/model/migrator/sqlite_adapter\"\n            SQLiteAdapter\n          when :postgres\n            require \"hanami/model/migrator/postgres_adapter\"\n            PostgresAdapter\n          when :mysql\n            require \"hanami/model/migrator/mysql_adapter\"\n            MySQLAdapter\n          else\n            self\n          end.new(connection)\n        end\n\n        # Initialize an adapter\n        #\n        # @since 0.4.0\n        # @api private\n        def initialize(connection)\n          @connection = connection\n        end\n\n        # Create database.\n        # It must be implemented by subclasses.\n        #\n        # @since 0.4.0\n        # @api private\n        #\n        # @see Hanami::Model::Migrator.create\n        def create\n          raise MigrationError.new(\"Current adapter (#{connection.database_type}) doesn't support create.\")\n        end\n\n        # Drop database.\n        # It must be implemented by subclasses.\n        #\n        # @since 0.4.0\n        # @api private\n        #\n        # @see Hanami::Model::Migrator.drop\n        def drop\n          raise MigrationError.new(\"Current adapter (#{connection.database_type}) doesn't support drop.\")\n        end\n\n        # @since 0.4.0\n        # @api private\n        def migrate(migrations, version)\n          version = Integer(version) unless version.nil?\n\n          Sequel::Migrator.run(connection.raw, migrations, target: version, allow_missing_migration_files: true)\n        rescue Sequel::Migrator::Error => exception\n          raise MigrationError.new(exception.message)\n        end\n\n        # @since 1.1.0\n        # @api private\n        def rollback(migrations, steps)\n          table = migrations_table_dataset\n          version = version_to_rollback(table, steps)\n\n          Sequel::Migrator.run(connection.raw, migrations, target: version, allow_missing_migration_files: true)\n        rescue Sequel::Migrator::Error => exception\n          raise MigrationError.new(exception.message)\n        end\n\n        # Load database schema.\n        # It must be implemented by subclasses.\n        #\n        # @since 0.4.0\n        # @api private\n        #\n        # @see Hanami::Model::Migrator.prepare\n        def load\n          raise MigrationError.new(\"Current adapter (#{connection.database_type}) doesn't support load.\")\n        end\n\n        # Database version.\n        #\n        # @since 0.4.0\n        # @api private\n        def version\n          table = migrations_table_dataset\n          return if table.nil?\n\n          record = table.order(MIGRATIONS_TABLE_VERSION_COLUMN).last\n          return if record.nil?\n\n          record.fetch(MIGRATIONS_TABLE_VERSION_COLUMN).scan(MIGRATIONS_FILE_NAME_PATTERN).first.to_s\n        end\n\n        private\n\n        # @since 1.1.0\n        # @api private\n        MIGRATIONS_FILE_NAME_PATTERN = /\\A[\\d]{14}/.freeze\n\n        # @since 1.1.0\n        # @api private\n        def version_to_rollback(table, steps)\n          record = table.order(Sequel.desc(MIGRATIONS_TABLE_VERSION_COLUMN)).all[steps]\n          return 0 unless record\n\n          record.fetch(MIGRATIONS_TABLE_VERSION_COLUMN).scan(MIGRATIONS_FILE_NAME_PATTERN).first.to_i\n        end\n\n        # @since 1.1.0\n        # @api private\n        def migrations_table_dataset\n          connection.table(MIGRATIONS_TABLE)\n        end\n\n        # @since 0.5.0\n        # @api private\n        attr_reader :connection\n\n        # @since 0.4.0\n        # @api private\n        def schema\n          connection.schema\n        end\n\n        # Returns a database connection\n        #\n        # Given a DB connection URI we can connect to a specific database or not, we need this when creating\n        # or dropping a database. Important to notice that we can't always open a _global_ DB connection,\n        # because most of the times application's DB user has no rights to do so.\n        #\n        # @param global [Boolean] determine whether or not a connection should specify a database.\n        #\n        # @since 0.5.0\n        # @api private\n        def new_connection(global: false)\n          uri = global ? connection.global_uri : connection.uri\n\n          Sequel.connect(uri)\n        end\n\n        # @since 0.4.0\n        # @api private\n        def database\n          escape connection.database\n        end\n\n        # @since 0.4.0\n        # @api private\n        def port\n          escape connection.port\n        end\n\n        # @since 0.4.0\n        # @api private\n        def host\n          escape connection.host\n        end\n\n        # @since 0.4.0\n        # @api private\n        def username\n          escape connection.user\n        end\n\n        # @since 0.4.0\n        # @api private\n        def password\n          escape connection.password\n        end\n\n        # @since 0.4.0\n        # @api private\n        def migrations_table\n          escape MIGRATIONS_TABLE\n        end\n\n        # @since 0.4.0\n        # @api private\n        def escape(string)\n          Shellwords.escape(string) unless string.nil?\n        end\n\n        # @since 1.0.2\n        # @api private\n        def execute(command, env: {}, error: ->(err) { raise MigrationError.new(err) })\n          Open3.popen3(env, command) do |_, stdout, stderr, wait_thr|\n            error.call(stderr.read) unless wait_thr.value.success?\n            yield stdout if block_given?\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/migrator/connection.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"cgi\"\n\nmodule Hanami\n  module Model\n    class Migrator\n      # Sequel connection wrapper\n      #\n      # Normalize external adapters interfaces\n      #\n      # @since 0.5.0\n      # @api private\n      class Connection\n        # @since 0.5.0\n        # @api private\n        def initialize(configuration)\n          @configuration = configuration\n        end\n\n        # @since 0.7.0\n        # @api private\n        def raw\n          @raw ||= begin\n                     Sequel.connect(\n                       configuration.url,\n                       loggers: [configuration.migrations_logger]\n                     )\n                   rescue Sequel::AdapterNotFound\n                     raise MigrationError.new(\"Current adapter (#{configuration.adapter.type}) doesn't support SQL database operations.\")\n                   end\n        end\n\n        # Returns DB connection host\n        #\n        # Even when adapter doesn't provide it explicitly it tries to parse\n        #\n        # @since 0.5.0\n        # @api private\n        def host\n          @host ||= parsed_uri.host || parsed_opt(\"host\")\n        end\n\n        # Returns DB connection port\n        #\n        # Even when adapter doesn't provide it explicitly it tries to parse\n        #\n        # @since 0.5.0\n        # @api private\n        def port\n          @port ||= parsed_uri.port || parsed_opt(\"port\").to_i.nonzero?\n        end\n\n        # Returns DB name from conenction\n        #\n        # Even when adapter doesn't provide it explicitly it tries to parse\n        #\n        # @since 0.5.0\n        # @api private\n        def database\n          @database ||= parsed_uri.path[1..-1]\n        end\n\n        # Returns DB type\n        #\n        # @example\n        #   connection.database_type\n        #     # => 'postgres'\n        #\n        # @since 0.5.0\n        # @api private\n        def database_type\n          case uri\n          when /sqlite/\n            :sqlite\n          when /postgres/\n            :postgres\n          when /mysql/\n            :mysql\n          end\n        end\n\n        # Returns user from DB connection\n        #\n        # Even when adapter doesn't provide it explicitly it tries to parse\n        #\n        # @since 0.5.0\n        # @api private\n        def user\n          @user ||= parsed_opt(\"user\") || parsed_uri.user\n        end\n\n        # Returns user from DB connection\n        #\n        # Even when adapter doesn't provide it explicitly it tries to parse\n        #\n        # @since 0.5.0\n        # @api private\n        def password\n          @password ||= parsed_opt(\"password\") || parsed_uri.password\n        end\n\n        # Returns DB connection URI directly from adapter\n        #\n        # @since 0.5.0\n        # @api private\n        def uri\n          @configuration.url\n        end\n\n        # Returns DB connection wihout specifying database name\n        #\n        # @since 0.5.0\n        # @api private\n        def global_uri\n          uri.sub(parsed_uri.select(:path).first, \"\")\n        end\n\n        # Returns a boolean telling if a DB connection is from JDBC or not\n        #\n        # @since 0.5.0\n        # @api private\n        def jdbc?\n          !uri.scan(\"jdbc:\").empty?\n        end\n\n        # Returns database connection URI instance without JDBC namespace\n        #\n        # @since 0.5.0\n        # @api private\n        def parsed_uri\n          @parsed_uri ||= URI.parse(uri.sub(\"jdbc:\", \"\"))\n        end\n\n        # @api private\n        def schema\n          configuration.schema\n        end\n\n        # Return the database table for the given name\n        #\n        # @since 0.7.0\n        # @api private\n        def table(name)\n          raw[name] if raw.tables.include?(name)\n        end\n\n        private\n\n        # @since 1.0.0\n        # @api private\n        attr_reader :configuration\n\n        # Returns a value of a given query string param\n        #\n        # @param option [String] which option from database connection will be extracted from URI\n        #\n        # @since 0.5.0\n        # @api private\n        def parsed_opt(option, query: parsed_uri.query)\n          return if query.nil?\n\n          @parsed_query_opts ||= CGI.parse(query)\n          @parsed_query_opts[option].to_a.last\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/migrator/logger.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"hanami/logger\"\n\nmodule Hanami\n  module Model\n    class Migrator\n      # Automatic logger for migrations\n      #\n      # @since 1.0.0\n      # @api private\n      class Logger < Hanami::Logger\n        # Formatter for migrations logger\n        #\n        # @since 1.0.0\n        # @api private\n        class Formatter < Hanami::Logger::Formatter\n          private\n\n          # @since 1.0.0\n          # @api private\n          def _format(hash)\n            \"[hanami] [#{hash.fetch(:severity)}] #{hash.fetch(:message)}\\n\"\n          end\n        end\n\n        # @since 1.0.0\n        # @api private\n        def initialize(stream)\n          super(nil, stream: stream, formatter: Formatter.new)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/migrator/mysql_adapter.rb",
    "content": "# frozen_string_literal: true\n\nmodule Hanami\n  module Model\n    class Migrator\n      # MySQL adapter\n      #\n      # @since 0.4.0\n      # @api private\n      class MySQLAdapter < Adapter\n        # @since 0.7.0\n        # @api private\n        PASSWORD = \"MYSQL_PWD\"\n\n        # @since 1.3.3\n        # @api private\n        DEFAULT_PORT = 3306\n\n        # @since 1.0.0\n        # @api private\n        DB_CREATION_ERROR = \"Database creation failed. If the database exists, \" \\\n                            \"then its console may be open. See this issue for more details: \" \\\n                            \"https://github.com/hanami/model/issues/250\"\n\n        # @since 0.4.0\n        # @api private\n        def create\n          new_connection(global: true).run %(CREATE DATABASE `#{database}`;)\n        rescue Sequel::DatabaseError => exception\n          message = if exception.message.match(/database exists/)\n                      DB_CREATION_ERROR\n                    else\n                      exception.message\n                    end\n\n          raise MigrationError.new(message)\n        end\n\n        # @since 0.4.0\n        # @api private\n        def drop\n          new_connection(global: true).run %(DROP DATABASE `#{database}`;)\n        rescue Sequel::DatabaseError => exception\n          message = if exception.message.match(/doesn\\'t exist/)\n                      \"Cannot find database: #{database}\"\n                    else\n                      exception.message\n                    end\n\n          raise MigrationError.new(message)\n        end\n\n        # @since 0.4.0\n        # @api private\n        def dump\n          dump_structure\n          dump_migrations_data\n        end\n\n        # @since 0.4.0\n        # @api private\n        def load\n          load_structure\n        end\n\n        private\n\n        # @since 0.7.0\n        # @api private\n        def password\n          connection.password\n        end\n\n        def port\n          super || DEFAULT_PORT\n        end\n\n        # @since 0.4.0\n        # @api private\n        def dump_structure\n          execute \"mysqldump --host=#{host} --port=#{port} --user=#{username} --no-data --skip-comments --ignore-table=#{database}.#{migrations_table} #{database} > #{schema}\", env: {PASSWORD => password}\n        end\n\n        # @since 0.4.0\n        # @api private\n        def load_structure\n          execute(\"mysql --host=#{host} --port=#{port} --user=#{username} #{database} < #{escape(schema)}\", env: {PASSWORD => password}) if schema.exist?\n        end\n\n        # @since 0.4.0\n        # @api private\n        def dump_migrations_data\n          execute \"mysqldump --host=#{host} --port=#{port} --user=#{username} --skip-comments #{database} #{migrations_table} >> #{schema}\", env: {PASSWORD => password}\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/migrator/postgres_adapter.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"hanami/utils/blank\"\n\nmodule Hanami\n  module Model\n    class Migrator\n      # PostgreSQL adapter\n      #\n      # @since 0.4.0\n      # @api private\n      class PostgresAdapter < Adapter\n        # @since 0.4.0\n        # @api private\n        HOST     = \"PGHOST\"\n\n        # @since 0.4.0\n        # @api private\n        PORT     = \"PGPORT\"\n\n        # @since 0.4.0\n        # @api private\n        USER     = \"PGUSER\"\n\n        # @since 0.4.0\n        # @api private\n        PASSWORD = \"PGPASSWORD\"\n\n        # @since 1.0.0\n        # @api private\n        DB_CREATION_ERROR = \"createdb: database creation failed. If the database exists, \" \\\n                            \"then its console may be open. See this issue for more details: \" \\\n                            \"https://github.com/hanami/model/issues/250\"\n\n        # @since 0.4.0\n        # @api private\n        def create\n          call_db_command(\"createdb\")\n        end\n\n        # @since 0.4.0\n        # @api private\n        def drop\n          call_db_command(\"dropdb\")\n        end\n\n        # @since 0.4.0\n        # @api private\n        def dump\n          dump_structure\n          dump_migrations_data\n        end\n\n        # @since 0.4.0\n        # @api private\n        def load\n          load_structure\n        end\n\n        private\n\n        # @since 1.3.3\n        # @api private\n        def environment_variables\n          {}.tap do |env|\n            env[HOST] = host unless host.nil?\n            env[PORT] = port.to_s unless port.nil?\n            env[PASSWORD] = password unless password.nil?\n            env[USER] = username unless username.nil?\n          end\n        end\n\n        # @since 0.4.0\n        # @api private\n        def dump_structure\n          execute \"pg_dump -s -x -O -T #{migrations_table} -f #{escape(schema)} #{database}\", env: environment_variables\n        end\n\n        # @since 0.4.0\n        # @api private\n        def load_structure\n          return unless schema.exist?\n\n          execute \"psql -X -q -f #{escape(schema)} #{database}\", env: environment_variables\n        end\n\n        # @since 0.4.0\n        # @api private\n        def dump_migrations_data\n          error = ->(err) { raise MigrationError.new(err) unless err =~ /no matching tables/i }\n          execute \"pg_dump -t #{migrations_table} #{database} >> #{escape(schema)}\", error: error, env: environment_variables\n        end\n\n        # @since 0.5.1\n        # @api private\n        def call_db_command(command)\n          require \"open3\"\n\n          begin\n            Open3.popen3(environment_variables, command, database) do |_stdin, _stdout, stderr, wait_thr|\n              raise MigrationError.new(modified_message(stderr.read)) unless wait_thr.value.success? # wait_thr.value is the exit status\n            end\n          rescue SystemCallError => exception\n            raise MigrationError.new(modified_message(exception.message))\n          end\n        end\n\n        # @since 1.1.0\n        # @api private\n        def modified_message(original_message)\n          case original_message\n          when /already exists/\n            DB_CREATION_ERROR\n          when /does not exist/\n            \"Cannot find database: #{database}\"\n          when /No such file or directory/\n            \"Could not find executable in your PATH: `#{original_message.split.last}`\"\n          else\n            original_message\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/migrator/sqlite_adapter.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"pathname\"\nrequire \"hanami/utils\"\nrequire \"English\"\n\nmodule Hanami\n  module Model\n    class Migrator\n      # SQLite3 Migrator\n      #\n      # @since 0.4.0\n      # @api private\n      class SQLiteAdapter < Adapter\n        # No-op for in-memory databases\n        #\n        # @since 0.4.0\n        # @api private\n        module Memory\n          # @since 0.4.0\n          # @api private\n          def create\n          end\n\n          # @since 0.4.0\n          # @api private\n          def drop\n          end\n        end\n\n        # Initialize adapter\n        #\n        # @since 0.4.0\n        # @api private\n        def initialize(configuration)\n          super\n          extend Memory if memory?\n        end\n\n        # @since 0.4.0\n        # @api private\n        def create\n          path.dirname.mkpath\n          FileUtils.touch(path)\n        rescue Errno::EACCES, Errno::EPERM\n          raise MigrationError.new(\"Permission denied: #{path.sub(/\\A\\/\\//, '')}\")\n        end\n\n        # @since 0.4.0\n        # @api private\n        def drop\n          path.delete\n        rescue Errno::ENOENT\n          raise MigrationError.new(\"Cannot find database: #{path.sub(/\\A\\/\\//, '')}\")\n        end\n\n        # @since 0.4.0\n        # @api private\n        def dump\n          dump_structure\n          dump_migrations_data\n        end\n\n        # @since 0.4.0\n        # @api private\n        def load\n          load_structure\n        end\n\n        private\n\n        # @since 0.4.0\n        # @api private\n        def path\n          root.join(\n            @connection.uri.sub(/\\A(jdbc:sqlite:\\/\\/|sqlite:\\/\\/)/, \"\")\n          )\n        end\n\n        # @since 0.4.0\n        # @api private\n        def root\n          Hanami::Model.configuration.root\n        end\n\n        # @since 0.4.0\n        # @api private\n        def memory?\n          uri = path.to_s\n          uri.match(/sqlite\\:\\/\\z/) ||\n            uri.match(/\\:memory\\:/)\n        end\n\n        # @since 0.4.0\n        # @api private\n        def dump_structure\n          execute \"sqlite3 #{escape(path)} .schema > #{escape(schema)}\"\n        end\n\n        # @since 0.4.0\n        # @api private\n        def load_structure\n          execute \"sqlite3 #{escape(path)} < #{escape(schema)}\" if schema.exist?\n        end\n\n        # @since 0.4.0\n        # @api private\n        #\n        def dump_migrations_data\n          execute \"sqlite3 #{escape(path)} .dump\" do |stdout|\n            begin\n              contents = stdout.read.split($INPUT_RECORD_SEPARATOR)\n              contents = contents.grep(/^INSERT INTO \"?#{migrations_table}\"?/)\n\n              ::File.open(schema, ::File::CREAT | ::File::BINARY | ::File::WRONLY | ::File::APPEND) do |file|\n                file.write(contents.join($INPUT_RECORD_SEPARATOR))\n              end\n            rescue => exception\n              raise MigrationError.new(exception.message)\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/migrator.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"sequel\"\nrequire \"sequel/extensions/migration\"\n\nmodule Hanami\n  module Model\n    # Migration error\n    #\n    # @since 0.4.0\n    class MigrationError < Hanami::Model::Error\n    end\n\n    # Database schema migrator\n    #\n    # @since 0.4.0\n    class Migrator\n      require \"hanami/model/migrator/connection\"\n      require \"hanami/model/migrator/adapter\"\n\n      # Create database defined by current configuration.\n      #\n      # It's only implemented for the following databases:\n      #\n      #   * SQLite3\n      #   * PostgreSQL\n      #   * MySQL\n      #\n      # @raise [Hanami::Model::MigrationError] if an error occurs\n      #\n      # @since 0.4.0\n      #\n      # @see Hanami::Model::Configuration#adapter\n      #\n      # @example\n      #   require 'hanami/model'\n      #   require 'hanami/model/migrator'\n      #\n      #   Hanami::Model.configure do\n      #     # ...\n      #     adapter :sql, 'postgres://localhost/foo'\n      #   end\n      #\n      #   Hanami::Model::Migrator.create # Creates `foo' database\n      #\n      # NOTE: Class level interface SHOULD be removed in Hanami 2.0\n      def self.create\n        new.create\n      end\n\n      # Drop database defined by current configuration.\n      #\n      # It's only implemented for the following databases:\n      #\n      #   * SQLite3\n      #   * PostgreSQL\n      #   * MySQL\n      #\n      # @raise [Hanami::Model::MigrationError] if an error occurs\n      #\n      # @since 0.4.0\n      #\n      # @see Hanami::Model::Configuration#adapter\n      #\n      # @example\n      #   require 'hanami/model'\n      #   require 'hanami/model/migrator'\n      #\n      #   Hanami::Model.configure do\n      #     # ...\n      #     adapter :sql, 'postgres://localhost/foo'\n      #   end\n      #\n      #   Hanami::Model::Migrator.drop # Drops `foo' database\n      #\n      # NOTE: Class level interface SHOULD be removed in Hanami 2.0\n      def self.drop\n        new.drop\n      end\n\n      # Migrate database schema\n      #\n      # It's possible to migrate \"down\" by specifying a version\n      # (eg. <tt>\"20150610133853\"</tt>)\n      #\n      # @param version [String,NilClass] target version\n      #\n      # @raise [Hanami::Model::MigrationError] if an error occurs\n      #\n      # @since 0.4.0\n      #\n      # @see Hanami::Model::Configuration#adapter\n      # @see Hanami::Model::Configuration#migrations\n      # @see Hanami::Model::Configuration#rollback\n      #\n      # @example Migrate Up\n      #   require 'hanami/model'\n      #   require 'hanami/model/migrator'\n      #\n      #   Hanami::Model.configure do\n      #     # ...\n      #     adapter    :sql, 'postgres://localhost/foo'\n      #     migrations 'db/migrations'\n      #   end\n      #\n      #   # Reads all files from \"db/migrations\" and apply them\n      #   Hanami::Model::Migrator.migrate\n      #\n      # @example Migrate Down\n      #   require 'hanami/model'\n      #   require 'hanami/model/migrator'\n      #\n      #   Hanami::Model.configure do\n      #     # ...\n      #     adapter    :sql, 'postgres://localhost/foo'\n      #     migrations 'db/migrations'\n      #   end\n      #\n      #   # Reads all files from \"db/migrations\" and apply them\n      #   Hanami::Model::Migrator.migrate\n      #\n      #   # Migrate to a specific version\n      #   Hanami::Model::Migrator.migrate(version: \"20150610133853\")\n      #\n      # NOTE: Class level interface SHOULD be removed in Hanami 2.0\n      def self.migrate(version: nil)\n        new.migrate(version: version)\n      end\n\n      # Rollback database schema\n      #\n      # @param steps [Number,NilClass] number of versions to rollback\n      #\n      # @raise [Hanami::Model::MigrationError] if an error occurs\n      #\n      # @since 1.1.0\n      #\n      # @see Hanami::Model::Configuration#adapter\n      # @see Hanami::Model::Configuration#migrations\n      # @see Hanami::Model::Configuration#migrate\n      #\n      # @example Rollback\n      #   require 'hanami/model'\n      #   require 'hanami/model/migrator'\n      #\n      #   Hanami::Model.configure do\n      #     # ...\n      #     adapter    :sql, 'postgres://localhost/foo'\n      #     migrations 'db/migrations'\n      #   end\n      #\n      #   # Reads all files from \"db/migrations\" and apply them\n      #   Hanami::Model::Migrator.migrate\n      #\n      #   # By default only rollback one version\n      #   Hanami::Model::Migrator.rollback\n      #\n      #   # Use a hash passing a number of versions to rollback, it will rollbacks those versions\n      #   Hanami::Model::Migrator.rollback(versions: 2)\n      #\n      # NOTE: Class level interface SHOULD be removed in Hanami 2.0\n      def self.rollback(steps: 1)\n        new.rollback(steps: steps)\n      end\n\n      # Migrate, dump schema, delete migrations.\n      #\n      # This is an experimental feature.\n      # It may change or be removed in the future.\n      #\n      # Actively developed applications accumulate tons of migrations.\n      # In the long term they are hard to maintain and slow to execute.\n      #\n      # \"Apply\" feature solves this problem.\n      #\n      # It keeps an updated SQL file with the structure of the database.\n      # This file can be used to create fresh databases for developer machines\n      # or during testing. This is faster than to run dozen or hundred migrations.\n      #\n      # When we use \"apply\", it eliminates all the migrations that are no longer\n      # necessary.\n      #\n      # @raise [Hanami::Model::MigrationError] if an error occurs\n      #\n      # @since 0.4.0\n      #\n      # @see Hanami::Model::Configuration#adapter\n      # @see Hanami::Model::Configuration#migrations\n      #\n      # @example Apply Migrations\n      #   require 'hanami/model'\n      #   require 'hanami/model/migrator'\n      #\n      #   Hanami::Model.configure do\n      #     # ...\n      #     adapter    :sql, 'postgres://localhost/foo'\n      #     migrations 'db/migrations'\n      #     schema     'db/schema.sql'\n      #   end\n      #\n      #   # Reads all files from \"db/migrations\" and apply and delete them.\n      #   # It generates an updated version of \"db/schema.sql\"\n      #   Hanami::Model::Migrator.apply\n      #\n      # NOTE: Class level interface SHOULD be removed in Hanami 2.0\n      def self.apply\n        new.apply\n      end\n\n      # Prepare database: drop, create, load schema (if any), migrate.\n      #\n      # This is designed for development machines and testing mode.\n      # It works faster if used with <tt>apply</tt>.\n      #\n      # @raise [Hanami::Model::MigrationError] if an error occurs\n      #\n      # @since 0.4.0\n      #\n      # @see Hanami::Model::Migrator.apply\n      #\n      # @example Prepare Database\n      #   require 'hanami/model'\n      #   require 'hanami/model/migrator'\n      #\n      #   Hanami::Model.configure do\n      #     # ...\n      #     adapter    :sql, 'postgres://localhost/foo'\n      #     migrations 'db/migrations'\n      #   end\n      #\n      #   Hanami::Model::Migrator.prepare # => creates `foo' and runs migrations\n      #\n      # @example Prepare Database (with schema dump)\n      #   require 'hanami/model'\n      #   require 'hanami/model/migrator'\n      #\n      #   Hanami::Model.configure do\n      #     # ...\n      #     adapter    :sql, 'postgres://localhost/foo'\n      #     migrations 'db/migrations'\n      #     schema     'db/schema.sql'\n      #   end\n      #\n      #   Hanami::Model::Migrator.apply   # => updates schema dump\n      #   Hanami::Model::Migrator.prepare # => creates `foo', load schema and run pending migrations (if any)\n      #\n      # NOTE: Class level interface SHOULD be removed in Hanami 2.0\n      def self.prepare\n        new.prepare\n      end\n\n      # Return current database version timestamp\n      #\n      # If no migrations were ran, it returns <tt>nil</tt>.\n      #\n      # @return [String,NilClass] current version, if previously migrated\n      #\n      # @since 0.4.0\n      #\n      # @example\n      #   # Given last migrations is:\n      #   #  20150610133853_create_books.rb\n      #\n      #   Hanami::Model::Migrator.version # => \"20150610133853\"\n      #\n      # NOTE: Class level interface SHOULD be removed in Hanami 2.0\n      def self.version\n        new.version\n      end\n\n      # Instantiate a new migrator\n      #\n      # @param configuration [Hanami::Model::Configuration] framework configuration\n      #\n      # @return [Hanami::Model::Migrator] a new instance\n      #\n      # @since 0.7.0\n      # @api private\n      def initialize(configuration: self.class.configuration)\n        @configuration = configuration\n        @adapter       = Adapter.for(configuration)\n      end\n\n      # @since 0.7.0\n      # @api private\n      #\n      # @see Hanami::Model::Migrator.create\n      def create\n        adapter.create\n      end\n\n      # @since 0.7.0\n      # @api private\n      #\n      # @see Hanami::Model::Migrator.drop\n      def drop\n        adapter.drop\n      end\n\n      # @since 0.7.0\n      # @api private\n      #\n      # @see Hanami::Model::Migrator.migrate\n      def migrate(version: nil)\n        adapter.migrate(migrations, version) if migrations?\n      end\n\n      # @since 1.1.0\n      # @api private\n      #\n      # @see Hanami::Model::Migrator.rollback\n      def rollback(steps: 1)\n        adapter.rollback(migrations, steps.abs) if migrations?\n      end\n\n      # @since 0.7.0\n      # @api private\n      #\n      # @see Hanami::Model::Migrator.apply\n      def apply\n        migrate\n        adapter.dump\n        delete_migrations\n      end\n\n      # @since 0.7.0\n      # @api private\n      #\n      # @see Hanami::Model::Migrator.prepare\n      def prepare\n        drop\n      rescue # rubocop:disable Lint/SuppressedException\n      ensure\n        create\n        adapter.load\n        migrate\n      end\n\n      # @since 0.7.0\n      # @api private\n      #\n      # @see Hanami::Model::Migrator.version\n      def version\n        adapter.version\n      end\n\n      # Hanami::Model configuration\n      #\n      # @since 0.4.0\n      # @api private\n      def self.configuration\n        Model.configuration\n      end\n\n      private\n\n      # @since 0.7.0\n      # @api private\n      attr_reader :configuration\n\n      # @since 0.7.0\n      # @api private\n      attr_reader :connection\n\n      # @since 0.7.0\n      # @api private\n      attr_reader :adapter\n\n      # Migrations directory\n      #\n      # @since 0.7.0\n      # @api private\n      def migrations\n        configuration.migrations\n      end\n\n      # Check if there are migrations\n      #\n      # @since 0.7.0\n      # @api private\n      def migrations?\n        Dir[\"#{migrations}/*.rb\"].any?\n      end\n\n      # Delete all the migrations\n      #\n      # @since 0.7.0\n      # @api private\n      def delete_migrations\n        migrations.each_child(&:delete)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/plugins/mapping.rb",
    "content": "# frozen_string_literal: true\n\nmodule Hanami\n  module Model\n    module Plugins\n      # Transform output into model domain types (entities).\n      #\n      # @since 0.7.0\n      # @api private\n      module Mapping\n        # Takes the output and applies the transformations\n        #\n        # @since 0.7.0\n        # @api private\n        class InputWithMapping < WrappingInput\n          # @since 0.7.0\n          # @api private\n          def initialize(relation, input)\n            super\n            @mapping = Hanami::Model.configuration.mappings[relation.name.to_sym]\n          end\n\n          # Processes the output\n          #\n          # @since 0.7.0\n          # @api private\n          def [](value)\n            @input[@mapping.process(value)]\n          end\n        end\n\n        # Class interface\n        #\n        # @since 0.7.0\n        # @api private\n        module ClassMethods\n          # Builds the output processor\n          #\n          # @since 0.7.0\n          # @api private\n          def build(relation, options = {})\n            wrapped_input = InputWithMapping.new(relation, options.fetch(:input) { input })\n            super(relation, options.merge(input: wrapped_input))\n          end\n        end\n\n        # @since 0.7.0\n        # @api private\n        def self.included(klass)\n          super\n\n          klass.extend ClassMethods\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/plugins/schema.rb",
    "content": "# frozen_string_literal: true\n\nmodule Hanami\n  module Model\n    module Plugins\n      # Transform input values into database specific types (primitives).\n      #\n      # @since 0.7.0\n      # @api private\n      module Schema\n        # Takes the input and applies the values transformations.\n        #\n        # @since 0.7.0\n        # @api private\n        class InputWithSchema < WrappingInput\n          # @since 0.7.0\n          # @api private\n          def initialize(relation, input)\n            super\n            @schema = relation.input_schema\n          end\n\n          # Processes the input\n          #\n          # @since 0.7.0\n          # @api private\n          def [](value)\n            @schema[@input[value]]\n          end\n        end\n\n        # Class interface\n        #\n        # @since 0.7.0\n        # @api private\n        module ClassMethods\n          # Builds the input processor\n          #\n          # @since 0.7.0\n          # @api private\n          def build(relation, options = {})\n            wrapped_input = InputWithSchema.new(relation, options.fetch(:input) { input })\n            super(relation, options.merge(input: wrapped_input))\n          end\n        end\n\n        # @since 0.7.0\n        # @api private\n        def self.included(klass)\n          super\n\n          klass.extend ClassMethods\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/plugins/timestamps.rb",
    "content": "# frozen_string_literal: true\n\nmodule Hanami\n  module Model\n    module Plugins\n      # Automatically set/update timestamp columns for create/update commands\n      #\n      # @since 0.7.0\n      # @api private\n      module Timestamps\n        # Takes the input and applies the timestamp transformation.\n        # This is an \"abstract class\", please look at the subclasses for\n        # specific behaviors.\n        #\n        # @since 0.7.0\n        # @api private\n        class InputWithTimestamp < WrappingInput\n          # Conventional timestamp names\n          #\n          # @since 0.7.0\n          # @api private\n          TIMESTAMPS = %i[created_at updated_at].freeze\n\n          # @since 0.7.0\n          # @api private\n          def initialize(relation, input)\n            super\n            @timestamps = relation.columns & TIMESTAMPS\n          end\n\n          # Processes the input\n          #\n          # @since 0.7.0\n          # @api private\n          def [](value)\n            return @input[value] unless timestamps?\n\n            _touch(@input[value], Time.now)\n          end\n\n          protected\n\n          # @since 0.7.0\n          # @api private\n          def _touch(_value)\n            raise NoMethodError\n          end\n\n          private\n\n          # @since 0.7.0\n          # @api private\n          def timestamps?\n            !@timestamps.empty?\n          end\n        end\n\n        # Updates <tt>updated_at</tt> timestamp for update commands\n        #\n        # @since 0.7.0\n        # @api private\n        class InputWithUpdateTimestamp < InputWithTimestamp\n          protected\n\n          # @since 0.7.0\n          # @api private\n          def _touch(value, now)\n            value[:updated_at] ||= now if @timestamps.include?(:updated_at)\n            value\n          end\n        end\n\n        # Sets <tt>created_at</tt> and <tt>updated_at</tt> timestamps for create commands\n        #\n        # @since 0.7.0\n        # @api private\n        class InputWithCreateTimestamp < InputWithUpdateTimestamp\n          protected\n\n          # @since 0.7.0\n          # @api private\n          def _touch(value, now)\n            super\n            value[:created_at] ||= now if @timestamps.include?(:created_at)\n            value\n          end\n        end\n\n        # Class interface\n        #\n        # @since 0.7.0\n        # @api private\n        module ClassMethods\n          # Build an input processor according to the current command (create or update).\n          #\n          # @since 0.7.0\n          # @api private\n          def build(relation, options = {})\n            plugin = if self < ROM::Commands::Create\n                       InputWithCreateTimestamp\n                     else\n                       InputWithUpdateTimestamp\n                     end\n\n            wrapped_input = plugin.new(relation, options.fetch(:input) { input })\n            super(relation, options.merge(input: wrapped_input))\n          end\n        end\n\n        # @since 0.7.0\n        # @api private\n        def self.included(klass)\n          super\n\n          klass.extend ClassMethods\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/plugins.rb",
    "content": "# frozen_string_literal: true\n\nmodule Hanami\n  module Model\n    # Plugins to extend read/write operations from/to the database\n    #\n    # @since 0.7.0\n    # @api private\n    module Plugins\n      # Wrapping input\n      #\n      # @since 0.7.0\n      # @api private\n      class WrappingInput\n        # @since 0.7.0\n        # @api private\n        def initialize(_relation, input)\n          @input = input || Hash\n        end\n      end\n\n      require \"hanami/model/plugins/mapping\"\n      require \"hanami/model/plugins/schema\"\n      require \"hanami/model/plugins/timestamps\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/relation_name.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative \"entity_name\"\nrequire \"hanami/utils/string\"\n\nmodule Hanami\n  module Model\n    # Conventional name for relations.\n    #\n    # Given a repository named <tt>SourceFileRepository</tt>, the associated\n    # relation will be <tt>:source_files</tt>.\n    #\n    # @since 0.7.0\n    # @api private\n    class RelationName < EntityName\n      # @param name [Class,String] the class or its name\n      # @return [String] the relation name\n      #\n      # @since 0.7.0\n      # @api private\n      def self.new(name)\n        Utils::String.transform(super, :underscore, :pluralize)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/sql/console.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"uri\"\n\nmodule Hanami\n  module Model\n    module Sql\n      # SQL console\n      #\n      # @since 0.7.0\n      # @api private\n      class Console\n        extend Forwardable\n\n        # @since 0.7.0\n        # @api private\n        def_delegator :console, :connection_string\n\n        # @since 0.7.0\n        # @api private\n        def initialize(uri)\n          @uri = URI.parse(uri)\n        end\n\n        private\n\n        # @since 0.7.0\n        # @api private\n        def console\n          case @uri.scheme\n          when \"sqlite\"\n            require \"hanami/model/sql/consoles/sqlite\"\n            Sql::Consoles::Sqlite.new(@uri)\n          when \"postgres\", \"postgresql\"\n            require \"hanami/model/sql/consoles/postgresql\"\n            Sql::Consoles::Postgresql.new(@uri)\n          when \"mysql\", \"mysql2\"\n            require \"hanami/model/sql/consoles/mysql\"\n            Sql::Consoles::Mysql.new(@uri)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/sql/consoles/abstract.rb",
    "content": "# frozen_string_literal: true\n\nmodule Hanami\n  module Model\n    module Sql\n      module Consoles\n        # Abstract adapter\n        #\n        # @since 0.7.0\n        # @api private\n        class Abstract\n          # @since 0.7.0\n          # @api private\n          def initialize(uri)\n            @uri = uri\n          end\n\n          private\n\n          # @since 0.7.0\n          # @api private\n          def database_name\n            @uri.path.sub(/^\\//, \"\")\n          end\n\n          # @since 0.7.0\n          # @api private\n          def concat(*tokens)\n            tokens.join\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/sql/consoles/mysql.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative \"abstract\"\n\nmodule Hanami\n  module Model\n    module Sql\n      module Consoles\n        # MySQL adapter\n        #\n        # @since 0.7.0\n        # @api private\n        class Mysql < Abstract\n          # @since 0.7.0\n          # @api private\n          COMMAND = \"mysql\"\n\n          # @since 0.7.0\n          # @api private\n          def connection_string\n            concat(command, host, database, port, username, password)\n          end\n\n          private\n\n          # @since 0.7.0\n          # @api private\n          def command\n            COMMAND\n          end\n\n          # @since 0.7.0\n          # @api private\n          def host\n            \" -h #{@uri.host}\"\n          end\n\n          # @since 0.7.0\n          # @api private\n          def database\n            \" -D #{database_name}\"\n          end\n\n          # @since 0.7.0\n          # @api private\n          def port\n            \" -P #{@uri.port}\" unless @uri.port.nil?\n          end\n\n          # @since 0.7.0\n          # @api private\n          def username\n            \" -u #{@uri.user}\" unless @uri.user.nil?\n          end\n\n          # @since 0.7.0\n          # @api private\n          def password\n            \" -p #{@uri.password}\" unless @uri.password.nil?\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/sql/consoles/postgresql.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative \"abstract\"\nrequire \"cgi\"\n\nmodule Hanami\n  module Model\n    module Sql\n      module Consoles\n        # PostgreSQL adapter\n        #\n        # @since 0.7.0\n        # @api private\n        class Postgresql < Abstract\n          # @since 0.7.0\n          # @api private\n          COMMAND = \"psql\"\n\n          # @since 0.7.0\n          # @api private\n          PASSWORD = \"PGPASSWORD\"\n\n          # @since 0.7.0\n          # @api private\n          def connection_string\n            configure_password\n            concat(command, host, database, port, username)\n          end\n\n          private\n\n          # @since 0.7.0\n          # @api private\n          def command\n            COMMAND\n          end\n\n          # @since 0.7.0\n          # @api private\n          def host\n            \" -h #{query['host'] || @uri.host}\"\n          end\n\n          # @since 0.7.0\n          # @api private\n          def database\n            \" -d #{database_name}\"\n          end\n\n          # @since 0.7.0\n          # @api private\n          def port\n            port = query[\"port\"] || @uri.port\n            \" -p #{port}\" if port\n          end\n\n          # @since 0.7.0\n          # @api private\n          def username\n            username = query[\"user\"] || @uri.user\n            \" -U #{username}\" if username\n          end\n\n          # @since 0.7.0\n          # @api private\n          def configure_password\n            password = query[\"password\"] || @uri.password\n            ENV[PASSWORD] = CGI.unescape(query[\"password\"] || @uri.password) if password\n          end\n\n          # @since 1.1.0\n          # @api private\n          def query\n            return {} if @uri.query.nil? || @uri.query.empty?\n\n            parsed_query = @uri.query.split(\"&\").map { |a| a.split(\"=\") }\n            @query ||= Hash[parsed_query]\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/sql/consoles/sqlite.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative \"abstract\"\nrequire \"shellwords\"\n\nmodule Hanami\n  module Model\n    module Sql\n      module Consoles\n        # SQLite adapter\n        #\n        # @since 0.7.0\n        # @api private\n        class Sqlite < Abstract\n          # @since 0.7.0\n          # @api private\n          COMMAND = \"sqlite3\"\n\n          # @since 0.7.0\n          # @api private\n          def connection_string\n            concat(command, \" \", host, database)\n          end\n\n          private\n\n          # @since 0.7.0\n          # @api private\n          def command\n            COMMAND\n          end\n\n          # @since 0.7.0\n          # @api private\n          def host\n            @uri.host unless @uri.host.nil?\n          end\n\n          # @since 0.7.0\n          # @api private\n          def database\n            Shellwords.escape(@uri.path)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/sql/entity/schema.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"hanami/entity/schema\"\nrequire \"hanami/model/types\"\nrequire \"hanami/model/association\"\n\nmodule Hanami\n  module Model\n    module Sql\n      module Entity\n        # SQL Entity schema\n        #\n        # This schema setup is automatic.\n        #\n        # Hanami looks at the database columns, associations and potentially to\n        # the mapping in the repository (optional, only for legacy databases).\n        #\n        # @since 0.7.0\n        # @api private\n        #\n        # @see Hanami::Entity::Schema\n        class Schema < Hanami::Entity::Schema\n          # Build a new instance of Schema according to database columns,\n          # associations and potentially to mapping defined by the repository.\n          #\n          # @param registry [Hash] a registry that keeps reference between\n          #   entities class and their underscored names\n          # @param relation [ROM::Relation] the database relation\n          # @param mapping [Hanami::Model::Mapping] the optional repository\n          #   mapping\n          #\n          # @return [Hanami::Model::Sql::Entity::Schema] the schema\n          #\n          # @since 0.7.0\n          # @api private\n          def initialize(registry, relation, mapping)\n            attributes  = build(registry, relation, mapping)\n            @schema     = Types::Coercible::Hash.schema(attributes)\n            @attributes = Hash[attributes.map { |k, _| [k, true] }]\n            freeze\n          end\n\n          # Process attributes\n          #\n          # @param attributes [#to_hash] the attributes hash\n          #\n          # @raise [TypeError] if the process fails\n          #\n          # @since 1.0.1\n          # @api private\n          def call(attributes)\n            schema.call(attributes)\n          end\n\n          # @since 1.0.1\n          # @api private\n          alias_method :[], :call\n\n          # Check if the attribute is known\n          #\n          # @param name [Symbol] the attribute name\n          #\n          # @return [TrueClass,FalseClass] the result of the check\n          #\n          # @since 0.7.0\n          # @api private\n          def attribute?(name)\n            attributes.key?(name)\n          end\n\n          private\n\n          # @since 0.7.0\n          # @api private\n          attr_reader :attributes\n\n          # Build the schema\n          #\n          # @param registry [Hash] a registry that keeps reference between\n          #   entities class and their underscored names\n          # @param relation [ROM::Relation] the database relation\n          # @param mapping [Hanami::Model::Mapping] the optional repository\n          #   mapping\n          #\n          # @return [Dry::Types::Constructor] the inner schema\n          #\n          # @since 0.7.0\n          # @api private\n          def build(registry, relation, mapping)\n            build_attributes(relation, mapping).merge(\n              build_associations(registry, relation.associations)\n            )\n          end\n\n          # Extract a set of attributes from the database table or from the\n          # optional repository mapping.\n          #\n          # @param relation [ROM::Relation] the database relation\n          # @param mapping [Hanami::Model::Mapping] the optional repository\n          #   mapping\n          #\n          # @return [Hash] a set of attributes\n          #\n          # @since 0.7.0\n          # @api private\n          def build_attributes(relation, mapping)\n            schema = relation.schema.to_h\n            schema.each_with_object({}) do |(attribute, type), result|\n              attribute = mapping.translate(attribute) if mapping.reverse?\n              result[attribute] = coercible(type)\n            end\n          end\n\n          # Merge attributes and associations\n          #\n          # @param registry [Hash] a registry that keeps reference between\n          #   entities class and their underscored names\n          # @param associations [ROM::AssociationSet] a set of associations for\n          #   the current relation\n          #\n          # @return [Hash] attributes with associations\n          #\n          # @since 0.7.0\n          # @api private\n          def build_associations(registry, associations)\n            associations.each_with_object({}) do |(name, association), result|\n              target       = registry.fetch(association.target.to_sym)\n              result[name] = Association.lookup(association).schema_type(target)\n            end\n          end\n\n          # Converts given ROM type into coercible type for entity attribute\n          #\n          # @since 0.7.0\n          # @api private\n          def coercible(type)\n            Types::Schema.coercible(type)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/sql/types/schema/coercions.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"hanami/utils/string\"\nrequire \"hanami/utils/hash\"\n\nmodule Hanami\n  module Model\n    module Sql\n      module Types\n        module Schema\n          # Coercions for schema types\n          #\n          # @since 0.7.0\n          # @api private\n          #\n          # rubocop:disable Metrics/ModuleLength\n          module Coercions\n            # Coerces given argument into Integer\n            #\n            # @param arg [#to_i,#to_int] the argument to coerce\n            #\n            # @return [Integer] the result of the coercion\n            #\n            # @raise [ArgumentError] if the coercion fails\n            #\n            # @since 0.7.0\n            # @api private\n            def self.int(arg)\n              case arg\n              when ::Integer\n                arg\n              when ::Float, ::BigDecimal, ::String, ::Hanami::Utils::String, ->(a) { a.respond_to?(:to_int) }\n                ::Kernel.Integer(arg)\n              else\n                raise ArgumentError.new(\"invalid value for Integer(): #{arg.inspect}\")\n              end\n            end\n\n            # Coerces given argument into Float\n            #\n            # @param arg [#to_f] the argument to coerce\n            #\n            # @return [Float] the result of the coercion\n            #\n            # @raise [ArgumentError] if the coercion fails\n            #\n            # @since 0.7.0\n            # @api private\n            def self.float(arg)\n              case arg\n              when ::Float\n                arg\n              when ::Integer, ::BigDecimal, ::String, ::Hanami::Utils::String, ->(a) { a.respond_to?(:to_f) && !a.is_a?(::Time) }\n                ::Kernel.Float(arg)\n              else\n                raise ArgumentError.new(\"invalid value for Float(): #{arg.inspect}\")\n              end\n            end\n\n            # Coerces given argument into BigDecimal\n            #\n            # @param arg [#to_d] the argument to coerce\n            #\n            # @return [BigDecimal] the result of the coercion\n            #\n            # @raise [ArgumentError] if the coercion fails\n            #\n            # @since 0.7.0\n            # @api private\n            def self.decimal(arg)\n              case arg\n              when ::BigDecimal\n                arg\n              when ::Integer, ::Float, ::String, ::Hanami::Utils::String\n                ::Kernel.BigDecimal(arg, ::Float::DIG)\n              when ->(a) { a.respond_to?(:to_d) }\n                arg.to_d\n              else\n                raise ArgumentError.new(\"invalid value for BigDecimal(): #{arg.inspect}\")\n              end\n            end\n\n            # Coerces given argument into Date\n            #\n            # @param arg [#to_date,String] the argument to coerce\n            #\n            # @return [Date] the result of the coercion\n            #\n            # @raise [ArgumentError] if the coercion fails\n            #\n            # @since 0.7.0\n            # @api private\n            def self.date(arg)\n              case arg\n              when ::Date\n                arg\n              when ::String, ::Hanami::Utils::String\n                ::Date.parse(arg)\n              when ::Time, ::DateTime, ->(a) { a.respond_to?(:to_date) }\n                arg.to_date\n              else\n                raise ArgumentError.new(\"invalid value for Date(): #{arg.inspect}\")\n              end\n            end\n\n            # Coerces given argument into DateTime\n            #\n            # @param arg [#to_datetime,String] the argument to coerce\n            #\n            # @return [DateTime] the result of the coercion\n            #\n            # @raise [ArgumentError] if the coercion fails\n            #\n            # @since 0.7.0\n            # @api private\n            def self.datetime(arg)\n              case arg\n              when ::DateTime\n                arg\n              when ::String, ::Hanami::Utils::String\n                ::DateTime.parse(arg)\n              when ::Date, ::Time, ->(a) { a.respond_to?(:to_datetime) }\n                arg.to_datetime\n              else\n                raise ArgumentError.new(\"invalid value for DateTime(): #{arg.inspect}\")\n              end\n            end\n\n            # Coerces given argument into Time\n            #\n            # @param arg [#to_time,String] the argument to coerce\n            #\n            # @return [Time] the result of the coercion\n            #\n            # @raise [ArgumentError] if the coercion fails\n            #\n            # @since 0.7.0\n            # @api private\n            def self.time(arg)\n              case arg\n              when ::Time\n                arg\n              when ::String, ::Hanami::Utils::String\n                ::Time.parse(arg)\n              when ::Date, ::DateTime, ->(a) { a.respond_to?(:to_time) }\n                arg.to_time\n              when ::Integer\n                ::Time.at(arg)\n              else\n                raise ArgumentError.new(\"invalid value for Time(): #{arg.inspect}\")\n              end\n            end\n\n            # Coerces given argument into Array\n            #\n            # @param arg [#to_ary] the argument to coerce\n            #\n            # @return [Array] the result of the coercion\n            #\n            # @raise [ArgumentError] if the coercion fails\n            #\n            # @since 0.7.0\n            # @api private\n            def self.array(arg)\n              case arg\n              when ::Array\n                arg\n              when ->(a) { a.respond_to?(:to_ary) }\n                ::Kernel.Array(arg)\n              else\n                raise ArgumentError.new(\"invalid value for Array(): #{arg.inspect}\")\n              end\n            end\n\n            # Coerces given argument into Hash\n            #\n            # @param arg [#to_hash] the argument to coerce\n            #\n            # @return [Hash] the result of the coercion\n            #\n            # @raise [ArgumentError] if the coercion fails\n            #\n            # @since 0.7.0\n            # @api private\n            def self.hash(arg)\n              case arg\n              when ::Hash\n                arg\n              when ->(a) { a.respond_to?(:to_hash) }\n                Utils::Hash.deep_symbolize(\n                  ::Kernel.Hash(arg)\n                )\n              else\n                raise ArgumentError.new(\"invalid value for Hash(): #{arg.inspect}\")\n              end\n            end\n\n            # Coerces given argument to appropriate Postgres JSON(B) type, i.e. Hash or Array\n            #\n            # @param arg [Object] the object to coerce\n            #\n            # @return [Hash, Array] the result of the coercion\n            #\n            # @raise [ArgumentError] if the coercion fails\n            #\n            # @since 1.0.2\n            # @api private\n            def self.pg_json(arg)\n              case arg\n              when ->(a) { a.respond_to?(:to_hash) }\n                hash(arg)\n              when ->(a) { a.respond_to?(:to_a) }\n                array(arg)\n              else\n                raise ArgumentError.new(\"invalid value for PG_JSON(): #{arg.inspect}\")\n              end\n            end\n          end\n\n          # rubocop:enable Metrics/ModuleLength\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/sql/types.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"hanami/model/types\"\nrequire \"rom/types\"\n\nmodule Hanami\n  module Model\n    module Sql\n      # Types definitions for SQL databases\n      #\n      # @since 0.7.0\n      module Types\n        include Dry::Types.module\n\n        # Types for schema definitions\n        #\n        # @since 0.7.0\n        module Schema\n          require \"hanami/model/sql/types/schema/coercions\"\n\n          String   = Types::Optional::Coercible::String\n\n          Int      = Types::Strict::Nil | Types::Int.constructor(Coercions.method(:int))\n          Float    = Types::Strict::Nil | Types::Float.constructor(Coercions.method(:float))\n          Decimal  = Types::Strict::Nil | Types::Float.constructor(Coercions.method(:decimal))\n\n          Bool     = Types::Strict::Nil | Types::Strict::Bool\n\n          Date     = Types::Strict::Nil | Types::Date.constructor(Coercions.method(:date))\n          DateTime = Types::Strict::Nil | Types::DateTime.constructor(Coercions.method(:datetime))\n          Time     = Types::Strict::Nil | Types::Time.constructor(Coercions.method(:time))\n\n          Array    = Types::Strict::Nil | Types::Array.constructor(Coercions.method(:array))\n          Hash     = Types::Strict::Nil | Types::Hash.constructor(Coercions.method(:hash))\n\n          PG_JSON  = Types::Strict::Nil | Types::Any.constructor(Coercions.method(:pg_json))\n\n          # @since 0.7.0\n          # @api private\n          MAPPING = {\n            Types::String.pristine => Schema::String,\n            Types::Int.pristine => Schema::Int,\n            Types::Float.pristine => Schema::Float,\n            Types::Decimal.pristine => Schema::Decimal,\n            Types::Bool.pristine => Schema::Bool,\n            Types::Date.pristine => Schema::Date,\n            Types::DateTime.pristine => Schema::DateTime,\n            Types::Time.pristine => Schema::Time,\n            Types::Array.pristine => Schema::Array,\n            Types::Hash.pristine => Schema::Hash,\n            Types::String.optional.pristine => Schema::String,\n            Types::Int.optional.pristine => Schema::Int,\n            Types::Float.optional.pristine => Schema::Float,\n            Types::Decimal.optional.pristine => Schema::Decimal,\n            Types::Bool.optional.pristine => Schema::Bool,\n            Types::Date.optional.pristine => Schema::Date,\n            Types::DateTime.optional.pristine => Schema::DateTime,\n            Types::Time.optional.pristine => Schema::Time,\n            Types::Array.optional.pristine => Schema::Array,\n            Types::Hash.optional.pristine => Schema::Hash\n          }.freeze\n\n          # Convert given type into coercible\n          #\n          # @since 0.7.0\n          # @api private\n          def self.coercible(attribute)\n            return attribute if attribute.constrained?\n\n            type      = attribute.type\n            unwrapped = type.optional? ? type.right : type\n\n            # NOTE: In the future rom-sql should be able to always return Ruby\n            # types instead of Sequel types. When that will happen we can get\n            # rid of this logic in the block and fall back to:\n            #\n            #  MAPPING.fetch(unwrapped.pristine, attribute)\n            MAPPING.fetch(unwrapped.pristine) do\n              if pg_json?(unwrapped.pristine)\n                Schema::PG_JSON\n              else\n                attribute\n              end\n            end\n          end\n\n          # @since 1.0.4\n          # @api private\n          def self.pg_json_pristines\n            @pg_json_pristines ||= ::Hash.new do |hash, type|\n              hash[type] = (ROM::SQL::Types::PG.const_get(type).pristine if defined?(ROM::SQL::Types::PG))\n            end\n          end\n\n          # @since 1.0.2\n          # @api private\n          def self.pg_json?(pristine)\n            pristine == pg_json_pristines[\"JSONB\"] || # rubocop:disable Style/MultipleComparison\n              pristine == pg_json_pristines[\"JSON\"]\n          end\n\n          private_class_method :pg_json?\n\n          # Coercer for SQL associations target\n          #\n          # @since 0.7.0\n          # @api private\n          class AssociationType < Hanami::Model::Types::Schema::CoercibleType\n            # Check if value can be coerced\n            #\n            # @param value [Object] the value\n            #\n            # @return [TrueClass,FalseClass] the result of the check\n            #\n            # @since 0.7.0\n            # @api private\n            def valid?(value)\n              value.inspect =~ /\\[#{primitive}\\]/ || super\n            end\n\n            # @since 0.7.0\n            # @api private\n            def success(*args)\n              result(Dry::Types::Result::Success, primitive.new(args.first.to_h))\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/sql.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rom-sql\"\nrequire \"hanami/utils\"\n\nmodule Hanami\n  # Hanami::Model migrations\n  module Model\n    require \"hanami/model/error\"\n    require \"hanami/model/association\"\n    require \"hanami/model/migration\"\n\n    # Define a migration\n    #\n    # It must define an up/down strategy to write schema changes (up) and to\n    # rollback them (down).\n    #\n    # We can use <tt>up</tt> and <tt>down</tt> blocks for custom strategies, or\n    # only one <tt>change</tt> block that automatically implements \"down\" strategy.\n    #\n    # @param blk [Proc] a block that defines up/down or change database migration\n    #\n    # @since 0.4.0\n    #\n    # @example Use up/down blocks\n    #   Hanami::Model.migration do\n    #     up do\n    #       create_table :books do\n    #         primary_key :id\n    #         column :book, String\n    #       end\n    #     end\n    #\n    #     down do\n    #       drop_table :books\n    #     end\n    #   end\n    #\n    # @example Use change block\n    #   Hanami::Model.migration do\n    #     change do\n    #       create_table :books do\n    #         primary_key :id\n    #         column :book, String\n    #       end\n    #     end\n    #\n    #     # DOWN strategy is automatically generated\n    #   end\n    def self.migration(&blk)\n      Migration.new(configuration.gateways[:default], &blk)\n    end\n\n    # SQL adapter\n    #\n    # @since 0.7.0\n    module Sql\n      require \"hanami/model/sql/types\"\n      require \"hanami/model/sql/entity/schema\"\n\n      # Returns a SQL fragment that references a database function by the given name\n      # This is useful for database migrations\n      #\n      # @param name [String,Symbol] the function name\n      # @return [String] the SQL fragment\n      #\n      # @since 0.7.0\n      #\n      # @example\n      #   Hanami::Model.migration do\n      #     up do\n      #       execute 'CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"'\n      #\n      #       create_table :source_files do\n      #         column :id, 'uuid', primary_key: true, default: Hanami::Model::Sql.function(:uuid_generate_v4)\n      #         # ...\n      #       end\n      #     end\n      #\n      #     down do\n      #       drop_table :source_files\n      #       execute 'DROP EXTENSION \"uuid-ossp\"'\n      #     end\n      #   end\n      def self.function(name)\n        Sequel.function(name)\n      end\n\n      # Returns a literal SQL fragment for the given SQL fragment.\n      # This is useful for database migrations\n      #\n      # @param string [String] the SQL fragment\n      # @return [String] the literal SQL fragment\n      #\n      # @since 0.7.0\n      #\n      # @example\n      #   Hanami::Model.migration do\n      #     up do\n      #       execute %{\n      #         CREATE TYPE inventory_item AS (\n      #           name            text,\n      #           supplier_id     integer,\n      #           price           numeric\n      #         );\n      #       }\n      #\n      #       create_table :items do\n      #         column :item, 'inventory_item', default: Hanami::Model::Sql.literal(\"ROW('fuzzy dice', 42, 1.99)\")\n      #         # ...\n      #       end\n      #     end\n      #\n      #     down do\n      #       drop_table :items\n      #       execute 'DROP TYPE inventory_item'\n      #     end\n      #   end\n      def self.literal(string)\n        Sequel.lit(string)\n      end\n\n      # Returns SQL fragment for ascending order for the given column\n      #\n      # @param column [Symbol] the column name\n      # @return [String] the SQL fragment\n      #\n      # @since 0.7.0\n      def self.asc(column)\n        Sequel.asc(column)\n      end\n\n      # Returns SQL fragment for descending order for the given column\n      #\n      # @param column [Symbol] the column name\n      # @return [String] the SQL fragment\n      #\n      # @since 0.7.0\n      def self.desc(column)\n        Sequel.desc(column)\n      end\n    end\n\n    Error.register(ROM::SQL::DatabaseError,             DatabaseError)\n    Error.register(ROM::SQL::ConstraintError,           ConstraintViolationError)\n    Error.register(ROM::SQL::NotNullConstraintError,    NotNullConstraintViolationError)\n    Error.register(ROM::SQL::UniqueConstraintError,     UniqueConstraintViolationError)\n    Error.register(ROM::SQL::CheckConstraintError,      CheckConstraintViolationError)\n    Error.register(ROM::SQL::ForeignKeyConstraintError, ForeignKeyConstraintViolationError)\n    Error.register(ROM::SQL::UnknownDBTypeError,        UnknownDatabaseTypeError)\n    Error.register(ROM::SQL::MissingPrimaryKeyError,    MissingPrimaryKeyError)\n\n    Error.register(Java::JavaSql::SQLException, DatabaseError) if Utils.jruby?\n  end\nend\n\nSequel.default_timezone = :utc\n\nROM.plugins do\n  adapter :sql do\n    register :mapping,    Hanami::Model::Plugins::Mapping,    type: :command\n    register :schema,     Hanami::Model::Plugins::Schema,     type: :command\n    register :timestamps, Hanami::Model::Plugins::Timestamps, type: :command\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/types.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rom/types\"\n\nmodule Hanami\n  module Model\n    # Types definitions\n    #\n    # @since 0.7.0\n    module Types\n      include ROM::Types\n\n      # @since 0.7.0\n      # @api private\n      def self.included(mod)\n        mod.extend(ClassMethods)\n      end\n\n      # Class level interface\n      #\n      # @since 0.7.0\n      module ClassMethods\n        # Define an entity of the given type\n        #\n        # @param type [Hanami::Entity] an entity\n        #\n        # @since 1.1.0\n        #\n        # @example\n        #   require \"hanami/model\"\n        #\n        #   class Account < Hanami::Entity\n        #     attributes do\n        #       # ...\n        #       attribute :owner, Types::Entity(User)\n        #     end\n        #   end\n        #\n        #   account = Account.new(owner: User.new(name: \"Luca\"))\n        #   account.owner.class # => User\n        #   account.owner.name  # => \"Luca\"\n        #\n        #   account = Account.new(owner: { name: \"MG\" })\n        #   account.owner.class # => User\n        #   account.owner.name  # => \"MG\"\n        def Entity(type)\n          type = Schema::CoercibleType.new(type) unless type.is_a?(Dry::Types::Definition)\n          type\n        end\n\n        # Define an array of given type\n        #\n        # @param type [Object] an object\n        #\n        # @since 0.7.0\n        #\n        # @example\n        #   require \"hanami/model\"\n        #\n        #   class Account < Hanami::Entity\n        #     attributes do\n        #       # ...\n        #       attribute :users, Types::Collection(User)\n        #     end\n        #   end\n        #\n        #   account = Account.new(users: [User.new(name: \"Luca\")])\n        #   user    = account.users.first\n        #   user.class # => User\n        #   user.name  # => \"Luca\"\n        #\n        #   account = Account.new(users: [{ name: \"MG\" }])\n        #   user    = account.users.first\n        #   user.class # => User\n        #   user.name  # => \"MG\"\n        def Collection(type)\n          type = Schema::CoercibleType.new(type) unless type.is_a?(Dry::Types::Definition)\n          Types::Array.member(type)\n        end\n      end\n\n      # Types for schema definitions\n      #\n      # @since 0.7.0\n      module Schema\n        # Coercer for objects within custom schema definition\n        #\n        # @since 0.7.0\n        # @api private\n        class CoercibleType < Dry::Types::Definition\n          # Coerce given value into the wrapped object type\n          #\n          # @param value [Object] the value\n          #\n          # @return [Object] the coerced value of `object` type\n          #\n          # @raise [TypeError] if value can't be coerced\n          #\n          # @since 0.7.0\n          # @api private\n          def call(value)\n            return if value.nil?\n\n            if valid?(value)\n              coerce(value)\n            else\n              raise TypeError.new(\"#{value.inspect} must be coercible into #{object}\")\n            end\n          end\n\n          # Check if value can be coerced\n          #\n          # It is true if value is an instance of `object` type or if value\n          # responds to `#to_hash`.\n          #\n          # @param value [Object] the value\n          #\n          # @return [TrueClass,FalseClass] the result of the check\n          #\n          # @since 0.7.0\n          # @api private\n          def valid?(value)\n            value.is_a?(object) ||\n              value.respond_to?(:to_hash)\n          end\n\n          # Coerce given value into an instance of `object` type\n          #\n          # @param value [Object] the value\n          #\n          # @return [Object] the coerced value of `object` type\n          def coerce(value)\n            case value\n            when object\n              value\n            else\n              object.new(value.to_hash)\n            end\n          end\n\n          # @since 0.7.0\n          # @api private\n          def object\n            result = primitive\n            return result unless result.respond_to?(:primitive)\n\n            result.primitive\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model/version.rb",
    "content": "# frozen_string_literal: true\n\nmodule Hanami\n  module Model\n    # Defines the version\n    #\n    # @since 0.1.0\n    VERSION = \"1.3.3\"\n  end\nend\n"
  },
  {
    "path": "lib/hanami/model.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rom\"\nrequire \"concurrent\"\nrequire \"hanami/entity\"\nrequire \"hanami/repository\"\n\n# Hanami\n#\n# @since 0.1.0\nmodule Hanami\n  # Hanami persistence\n  #\n  # @since 0.1.0\n  module Model\n    require \"hanami/model/version\"\n    require \"hanami/model/error\"\n    require \"hanami/model/configuration\"\n    require \"hanami/model/configurator\"\n    require \"hanami/model/mapping\"\n    require \"hanami/model/plugins\"\n\n    # @api private\n    # @since 0.7.0\n    @__repositories__ = Concurrent::Array.new\n\n    class << self\n      # @since 0.7.0\n      # @api private\n      attr_reader :config\n\n      # @since 0.7.0\n      # @api private\n      attr_reader :loaded\n\n      # @since 0.7.0\n      # @api private\n      alias_method :loaded?, :loaded\n    end\n\n    # Configure the framework\n    #\n    # @since 0.1.0\n    #\n    # @example\n    #   require 'hanami/model'\n    #\n    #   Hanami::Model.configure do\n    #     adapter :sql, ENV['DATABASE_URL']\n    #\n    #     migrations 'db/migrations'\n    #     schema     'db/schema.sql'\n    #   end\n    def self.configure(&block)\n      @config = Configurator.build(&block)\n      self\n    end\n\n    # Current configuration\n    #\n    # @since 0.1.0\n    def self.configuration\n      @configuration ||= Configuration.new(config)\n    end\n\n    # @since 0.7.0\n    # @api private\n    def self.repositories\n      @__repositories__\n    end\n\n    # @since 0.7.0\n    # @api private\n    def self.container\n      raise \"Not loaded\" unless loaded?\n\n      @container\n    end\n\n    # @since 0.1.0\n    def self.load!(&blk)\n      @container = configuration.load!(repositories, &blk)\n      @loaded    = true\n    end\n\n    # Disconnect from the database\n    #\n    # This is useful for rebooting applications in production and to ensure that\n    # the framework prunes stale connections.\n    #\n    # @since 1.0.0\n    #\n    # @example With Full Stack Hanami Project\n    #   # config/puma.rb\n    #   # ...\n    #   on_worker_boot do\n    #     Hanami.boot\n    #   end\n    #\n    # @example With Standalone Hanami::Model\n    #   # config/puma.rb\n    #   # ...\n    #   on_worker_boot do\n    #     Hanami::Model.disconnect\n    #     Hanami::Model.load!\n    #   end\n    def self.disconnect\n      configuration.connection&.disconnect\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami/repository.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rom-repository\"\nrequire \"hanami/model/entity_name\"\nrequire \"hanami/model/relation_name\"\nrequire \"hanami/model/mapped_relation\"\nrequire \"hanami/model/associations/dsl\"\nrequire \"hanami/model/association\"\nrequire \"hanami/utils/class\"\nrequire \"hanami/utils/class_attribute\"\nrequire \"hanami/utils/io\"\n\nmodule Hanami\n  # Mediates between the entities and the persistence layer, by offering an API\n  # to query and execute commands on a database.\n  #\n  #\n  #\n  # By default, a repository is named after an entity, by appending the\n  # `Repository` suffix to the entity class name.\n  #\n  # @example\n  #   require 'hanami/model'\n  #\n  #   class Article < Hanami::Entity\n  #   end\n  #\n  #   # valid\n  #   class ArticleRepository < Hanami::Repository\n  #   end\n  #\n  #   # not valid for Article\n  #   class PostRepository < Hanami::Repository\n  #   end\n  #\n  # A repository is storage independent.\n  # All the queries and commands are delegated to the current adapter.\n  #\n  # This architecture has several advantages:\n  #\n  #   * Applications depend on an abstract API, instead of low level details\n  #     (Dependency Inversion principle)\n  #\n  #   * Applications depend on a stable API, that doesn't change if the\n  #     storage changes\n  #\n  #   * Developers can postpone storage decisions\n  #\n  #   * Isolates the persistence logic at a low level\n  #\n  # Hanami::Model is shipped with one adapter:\n  #\n  #   * SqlAdapter\n  #\n  #\n  #\n  # All the queries and commands are private.\n  # This decision forces developers to define intention revealing API, instead\n  # of leaking storage API details outside of a repository.\n  #\n  # @example\n  #   require 'hanami/model'\n  #\n  #   # This is bad for several reasons:\n  #   #\n  #   #  * The caller has an intimate knowledge of the internal mechanisms\n  #   #      of the Repository.\n  #   #\n  #   #  * The caller works on several levels of abstraction.\n  #   #\n  #   #  * It doesn't express a clear intent, it's just a chain of methods.\n  #   #\n  #   #  * The caller can't be easily tested in isolation.\n  #   #\n  #   #  * If we change the storage, we are forced to change the code of the\n  #   #    caller(s).\n  #\n  #   ArticleRepository.new.where(author_id: 23).order(:published_at).limit(8)\n  #\n  #\n  #\n  #   # This is a huge improvement:\n  #   #\n  #   #  * The caller doesn't know how the repository fetches the entities.\n  #   #\n  #   #  * The caller works on a single level of abstraction.\n  #   #    It doesn't even know about records, only works with entities.\n  #   #\n  #   #  * It expresses a clear intent.\n  #   #\n  #   #  * The caller can be easily tested in isolation.\n  #   #    It's just a matter of stubbing this method.\n  #   #\n  #   #  * If we change the storage, the callers aren't affected.\n  #\n  #   ArticleRepository.new.most_recent_by_author(author)\n  #\n  #   class ArticleRepository < Hanami::Repository\n  #     def most_recent_by_author(author, limit = 8)\n  #       articles.\n  #         where(author_id: author.id).\n  #           order(:published_at).\n  #           limit(limit)\n  #     end\n  #   end\n  #\n  # @since 0.1.0\n  #\n  # @see Hanami::Entity\n  # @see http://martinfowler.com/eaaCatalog/repository.html\n  # @see http://en.wikipedia.org/wiki/Dependency_inversion_principle\n  class Repository < ROM::Repository::Root\n    # Plugins for database commands\n    #\n    # @since 0.7.0\n    # @api private\n    #\n    # @see Hanami::Model::Plugins\n    COMMAND_PLUGINS = %i[schema mapping timestamps].freeze\n\n    # Configuration\n    #\n    # @since 0.7.0\n    # @api private\n    def self.configuration\n      Hanami::Model.configuration\n    end\n\n    # Container\n    #\n    # @since 0.7.0\n    # @api private\n    def self.container\n      Hanami::Model.container\n    end\n\n    # Define a new ROM::Command while preserving the defaults used by Hanami itself.\n    #\n    # It allows the user to define a new command to, for example,\n    # create many records at the same time and still get entities back.\n    #\n    # The first argument is the command and relation it will operate on.\n    #\n    # @return [ROM::Command] the created command\n    #\n    # @example\n    #   # In this example, calling the create_many method with and array of data,\n    #   # would result in the creation of records and return an Array of Task entities.\n    #\n    #   class TaskRepository < Hanami::Repository\n    #     def create_many(data)\n    #       command(create: :tasks, result: :many).call(data)\n    #     end\n    #   end\n    #\n    # @since 1.2.0\n    def command(*args, **opts, &block)\n      opts[:use] = COMMAND_PLUGINS | Array(opts[:use])\n      opts[:mapper] = opts.fetch(:mapper, Model::MappedRelation.mapper_name)\n      super(*args, **opts, &block)\n    end\n\n    # Define a database relation, which describes how data is fetched from the\n    # database.\n    #\n    # It auto-infers the underlying database table.\n    #\n    # @since 0.7.0\n    # @api private\n    #\n    def self.define_relation\n      a = @associations\n      s = @schema\n\n      configuration.relation(relation) do\n        if s.nil?\n          schema(infer: true) do\n            associations(&a) unless a.nil?\n          end\n        else\n          schema(&s)\n        end\n      end\n\n      relations(relation)\n      root(relation)\n      class_eval %{\n        def #{relation}\n          Hanami::Model::MappedRelation.new(@#{relation})\n        end\n      }, __FILE__, __LINE__ - 4\n    end\n\n    # Defines the mapping between a database table and an entity.\n    #\n    # It's also responsible to associate table columns to entity attributes.\n    #\n    # @since 0.7.0\n    # @api private\n    #\n    def self.define_mapping\n      self.entity = Utils::Class.load!(entity_name)\n      e = entity\n      m = @mapping\n\n      blk = lambda do |_|\n        model       e\n        register_as Model::MappedRelation.mapper_name\n        instance_exec(&m) unless m.nil?\n      end\n\n      root = self.root\n      configuration.mappers { define(root, &blk) }\n      configuration.define_mappings(root, &blk)\n      configuration.register_entity(relation, entity_name.underscore, e)\n    end\n\n    # It defines associations, by adding relations to the repository\n    #\n    # @since 0.7.0\n    # @api private\n    #\n    # @see Hanami::Model::Associations::Dsl\n    def self.define_associations\n      Model::Associations::Dsl.new(self, &@associations) unless @associations.nil?\n    end\n\n    # Declare associations for the repository\n    #\n    # NOTE: This is an experimental feature\n    #\n    # @since 0.7.0\n    # @api private\n    #\n    # @example\n    #   class BookRepository < Hanami::Repository\n    #     associations do\n    #       has_many :books\n    #     end\n    #   end\n    def self.associations(&blk)\n      @associations = blk\n    end\n\n    # Declare database schema\n    #\n    # NOTE: This should be used **only** when Hanami can't find a corresponding Ruby type for your column.\n    #\n    # @since 1.0.0\n    #\n    # @example\n    #   # In this example `name` is a PostgreSQL Enum type that we want to treat like a string.\n    #\n    #   class ColorRepository < Hanami::Repository\n    #     schema do\n    #       attribute :id,         Hanami::Model::Sql::Types::Int\n    #       attribute :name,       Hanami::Model::Sql::Types::String\n    #       attribute :created_at, Hanami::Model::Sql::Types::DateTime\n    #       attribute :updated_at, Hanami::Model::Sql::Types::DateTime\n    #     end\n    #   end\n    def self.schema(&blk)\n      @schema = blk\n    end\n\n    # Declare mapping between database columns and entity's attributes\n    #\n    # NOTE: This should be used **only** when there is a name mismatch (eg. in legacy databases).\n    #\n    # @since 0.7.0\n    #\n    # @example\n    #   class BookRepository < Hanami::Repository\n    #     self.relation = :t_operator\n    #\n    #     mapping do\n    #       attribute :id,   from: :operator_id\n    #       attribute :name, from: :s_name\n    #     end\n    #   end\n    def self.mapping(&blk)\n      @mapping = blk\n    end\n\n    # Define relations, mapping and associations\n    #\n    # @since 0.7.0\n    # @api private\n    def self.load!\n      define_relation\n      define_mapping\n      define_associations\n    end\n\n    # @since 0.7.0\n    # @api private\n    #\n    def self.inherited(klass)\n      klass.class_eval do\n        include Utils::ClassAttribute\n        auto_struct true\n\n        @associations = nil\n        @mapping      = nil\n        @schema       = nil\n\n        class_attribute :entity\n        class_attribute :entity_name\n        class_attribute :relation\n\n        Hanami::Utils::IO.silence_warnings do\n          def self.relation=(name)\n            @relation = name.to_sym\n          end\n        end\n\n        self.entity_name = Model::EntityName.new(name)\n        self.relation    = Model::RelationName.new(name)\n\n        commands :create, update: :by_pk, delete: :by_pk, mapper: Model::MappedRelation.mapper_name, use: COMMAND_PLUGINS\n        prepend Commands\n      end\n\n      Hanami::Model.repositories << klass\n    end\n\n    # Extend commands from ROM::Repository with error management\n    #\n    # @since 0.7.0\n    module Commands\n      # Create a new record\n      #\n      # @return [Hanami::Entity] a new created entity\n      #\n      # @raise [Hanami::Model::Error] an error in case the command fails\n      #\n      # @since 0.7.0\n      #\n      # @example Create From Hash\n      #   user = UserRepository.new.create(name: 'Luca')\n      #\n      # @example Create From Entity\n      #   entity = User.new(name: 'Luca')\n      #   user   = UserRepository.new.create(entity)\n      #\n      #   user.id   # => 23\n      #   entity.id # => nil - It doesn't mutate original entity\n      def create(*args)\n        super\n      rescue => exception\n        raise Hanami::Model::Error.for(exception)\n      end\n\n      # Update a record\n      #\n      # @return [Hanami::Entity] an updated entity\n      #\n      # @raise [Hanami::Model::Error] an error in case the command fails\n      #\n      # @since 0.7.0\n      #\n      # @example Update From Data\n      #   repository = UserRepository.new\n      #   user       = repository.create(name: 'Luca')\n      #\n      #   user       = repository.update(user.id, age: 34)\n      #\n      # @example Update From Entity\n      #   repository = UserRepository.new\n      #   user       = repository.create(name: 'Luca')\n      #\n      #   entity     = User.new(age: 34)\n      #   user       = repository.update(user.id, entity)\n      #\n      #   user.age  # => 34\n      #   entity.id # => nil - It doesn't mutate original entity\n      def update(*args)\n        super\n      rescue => exception\n        raise Hanami::Model::Error.for(exception)\n      end\n\n      # Delete a record\n      #\n      # @return [Hanami::Entity] a deleted entity\n      #\n      # @raise [Hanami::Model::Error] an error in case the command fails\n      #\n      # @since 0.7.0\n      #\n      # @example\n      #   repository = UserRepository.new\n      #   user       = repository.create(name: 'Luca')\n      #\n      #   user       = repository.delete(user.id)\n      def delete(*args)\n        super\n      rescue => exception\n        raise Hanami::Model::Error.for(exception)\n      end\n    end\n\n    # Initialize a new instance\n    #\n    # @return [Hanami::Repository] the new instance\n    #\n    # @since 0.7.0\n    def initialize\n      super(self.class.container)\n    end\n\n    # Find by primary key\n    #\n    # @return [Hanami::Entity,NilClass] the entity, if found\n    #\n    # @raise [Hanami::Model::MissingPrimaryKeyError] if the table doesn't\n    #   define a primary key\n    #\n    # @since 0.7.0\n    #\n    # @example\n    #   repository = UserRepository.new\n    #   user       = repository.create(name: 'Luca')\n    #\n    #   user       = repository.find(user.id)\n    def find(id)\n      root.by_pk(id).as(:entity).one\n    rescue => exception\n      raise Hanami::Model::Error.for(exception)\n    end\n\n    # Return all the records for the relation\n    #\n    # @return [Array<Hanami::Entity>] all the entities\n    #\n    # @since 0.7.0\n    #\n    # @example\n    #   UserRepository.new.all\n    def all\n      root.as(:entity).to_a\n    end\n\n    # Returns the first record for the relation\n    #\n    # @return [Hanami::Entity,NilClass] first entity, if any\n    #\n    # @since 0.7.0\n    #\n    # @example\n    #   UserRepository.new.first\n    def first\n      root.as(:entity).limit(1).one\n    end\n\n    # Returns the last record for the relation\n    #\n    # @return [Hanami::Entity,NilClass] last entity, if any\n    #\n    # @since 0.7.0\n    #\n    # @example\n    #   UserRepository.new.last\n    def last\n      root.as(:entity).limit(1).reverse.one\n    end\n\n    # Deletes all the records from the relation\n    #\n    # @since 0.7.0\n    #\n    # @example\n    #   UserRepository.new.clear\n    def clear\n      root.delete\n    end\n\n    private\n\n    # Returns an association\n    #\n    # NOTE: This is an experimental feature\n    #\n    # @since 0.7.0\n    # @api private\n    def assoc(target, subject = nil)\n      Hanami::Model::Association.build(self, target, subject)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/hanami-model.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"hanami/model\"\n"
  },
  {
    "path": "script/ci",
    "content": "#!/bin/bash\nset -euo pipefail\nIFS=$'\\n\\t'\n\nprepare_build() {\n  if [ -d coverage ]; then\n    rm -rf coverage\n  fi\n}\n\nprint_ruby_version() {\n  echo \"Using $(ruby -v)\"\n  echo\n}\n\nrun_code_quality_checks() {\n  bundle exec rubocop .\n}\n\nrun_unit_tests() {\n  bundle exec rake spec:unit --trace\n}\n\nupload_code_coverage() {\n  bundle exec rake codecov:upload\n}\n\nmain() {\n  prepare_build\n  print_ruby_version\n  run_code_quality_checks\n  run_unit_tests\n  upload_code_coverage\n}\n\nmain\n"
  },
  {
    "path": "spec/integration/hanami/model/associations/belongs_to_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe \"Associations (belongs_to)\" do\n  it \"returns nil if association wasn't preloaded\" do\n    repository = BookRepository.new\n    book       = repository.create(name: \"L\")\n    found      = repository.find(book.id)\n\n    expect(found.author).to be(nil)\n  end\n\n  it \"preloads the associated record\" do\n    repository = BookRepository.new\n    author = AuthorRepository.new.create(name: \"Michel Foucault\")\n    book   = repository.create(author_id: author.id, title: \"Surveiller et punir\")\n    found = repository.find_with_author(book.id)\n\n    expect(found).to eq(book)\n    expect(found.author).to eq(author)\n  end\n\n  it \"returns an author\" do\n    repository = BookRepository.new\n    author = AuthorRepository.new.create(name: \"Maurice Leblanc\")\n    book   = repository.create(author_id: author.id, title: \"L'Aguille Creuse\")\n    found = repository.author_for(book)\n\n    expect(found).to eq(author)\n  end\n\n  it \"returns nil if there's no associated record\" do\n    repository = BookRepository.new\n    book = repository.create(title: \"The no author book\")\n\n    expect { repository.find_with_author(book.id) }.to_not raise_error\n  end\nend\n"
  },
  {
    "path": "spec/integration/hanami/model/associations/has_many_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe \"Associations (has_many)\" do\n  let(:authors) { AuthorRepository.new }\n  let(:books) { BookRepository.new }\n\n  it \"returns nil if association wasn't preloaded\" do\n    author = authors.create(name: \"L\")\n    found = authors.find(author.id)\n\n    expect(found.books).to be_nil\n  end\n\n  it \"preloads associated records\" do\n    author = authors.create(name: \"Umberto Eco\")\n    book = books.create(author_id: author.id, title: \"Foucault Pendulum\")\n\n    found = authors.find_with_books(author.id)\n\n    expect(found).to eq(author)\n    expect(found.books).to eq([book])\n  end\n\n  it \"creates an object with a collection of associated objects\" do\n    author = authors.create_with_books(name: \"Henry Thoreau\", books: [{title: \"Walden\"}])\n\n    expect(author).to be_an_instance_of(Author)\n    expect(author.name).to eq(\"Henry Thoreau\")\n    expect(author.books).to be_an_instance_of(Array)\n    expect(author.books.first).to be_an_instance_of(Book)\n    expect(author.books.first.title).to eq(\"Walden\")\n  end\n\n  it \"creates associated records when it receives a collection of serializable data\" do\n    author = authors.create_with_books(name: \"Sandi Metz\", books: [BaseParams.new(title: \"Practical Object-Oriented Design in Ruby\")])\n\n    expect(author).to be_an_instance_of(Author)\n    expect(author.name).to eq(\"Sandi Metz\")\n    expect(author.books).to be_an_instance_of(Array)\n    expect(author.books.first).to be_an_instance_of(Book)\n    expect(author.books.first.title).to eq(\"Practical Object-Oriented Design in Ruby\")\n  end\n\n  ##############################################################################\n  # OPERATIONS                                                                 #\n  ##############################################################################\n\n  ##\n  # ADD\n  #\n  it \"adds an object to the collection\" do\n    author = authors.create(name: \"Alexandre Dumas\")\n    book = authors.add_book(author, title: \"The Count of Monte Cristo\")\n\n    expect(book.id).to_not be_nil\n    expect(book.title).to eq(\"The Count of Monte Cristo\")\n    expect(book.author_id).to eq(author.id)\n  end\n\n  it \"adds an object to the collection with serializable data\" do\n    author = authors.create(name: \"David Foster Wallace\")\n    book = authors.add_book(author, BaseParams.new(title: \"Infinite Jest\"))\n\n    expect(book.id).to_not be_nil\n    expect(book.title).to eq(\"Infinite Jest\")\n    expect(book.author_id).to eq(author.id)\n  end\n\n  ##\n  # REMOVE\n  #\n  it \"removes an object from the collection\" do\n    authors = AuthorRepository.new\n    books = BookRepository.new\n\n    # Book under test\n    author = authors.create(name: \"Douglas Adams\")\n    book = books.create(author_id: author.id, title: \"The Hitchhiker's Guide to the Galaxy\")\n\n    # Different book\n    a = authors.create(name: \"William Finnegan\")\n    b = books.create(author_id: a.id, title: \"Barbarian Days: A Surfing Life\")\n\n    authors.remove_book(author, book.id)\n\n    # Check the book under test has removed foreign key\n    found_book = books.find(book.id)\n    expect(found_book).to_not be_nil\n    expect(found_book.author_id).to be_nil\n\n    found_author = authors.find_with_books(author.id)\n    expect(found_author.books.map(&:id)).to_not include(found_book.id)\n\n    # Check that the other book was left untouched\n    found_b = books.find(b.id)\n    expect(found_b.author_id).to eq(a.id)\n  end\n\n  ##\n  # TO_A\n  #\n  it \"returns an array of books\" do\n    author = authors.create(name: \"Nikolai Gogol\")\n    expected = books.create(author_id: author.id, title: \"Dead Souls\")\n    expect(expected).to be_an_instance_of(Book)\n\n    actual = authors.books_for(author).to_a\n    expect(actual).to eq([expected])\n  end\n\n  ##\n  # EACH\n  #\n  it \"iterates through the books\" do\n    author = authors.create(name: \"José Saramago\")\n    expected = books.create(author_id: author.id, title: \"The Cave\")\n\n    actual = []\n    authors.books_for(author).each do |book|\n      expect(book).to be_an_instance_of(Book)\n      actual << book\n    end\n\n    expect(actual).to eq([expected])\n  end\n\n  ##\n  # MAP\n  #\n  it \"iterates through the books and returns an array\" do\n    author = authors.create(name: \"José Saramago\")\n    expected = books.create(author_id: author.id, title: \"The Cave\")\n    expect(expected).to be_an_instance_of(Book)\n\n    actual = authors.books_for(author).map { |book| book }\n    expect(actual).to eq([expected])\n  end\n\n  ##\n  # COUNT\n  #\n  it \"returns the count of the associated books\" do\n    author = authors.create(name: \"Fyodor Dostoevsky\")\n    books.create(author_id: author.id, title: \"Crime and Punishment\")\n    books.create(author_id: author.id, title: \"The Brothers Karamazov\")\n\n    expect(authors.books_count(author)).to eq(2)\n  end\n\n  it \"returns the count of on sale associated books\" do\n    author = authors.create(name: \"Steven Pinker\")\n    books.create(author_id: author.id, title: \"The Sense of Style\", on_sale: true)\n\n    expect(authors.on_sales_books_count(author)).to eq(1)\n  end\n\n  ##\n  # DELETE\n  #\n  it \"deletes all the books\" do\n    author = authors.create(name: \"Grazia Deledda\")\n    book = books.create(author_id: author.id, title: \"Reeds In The Wind\")\n\n    authors.delete_books(author)\n\n    expect(books.find(book.id)).to be_nil\n  end\n\n  it \"deletes scoped books\" do\n    author = authors.create(name: \"Harper Lee\")\n    book = books.create(author_id: author.id, title: \"To Kill A Mockingbird\")\n    on_sale = books.create(author_id: author.id, title: \"Go Set A Watchman\", on_sale: true)\n\n    authors.delete_on_sales_books(author)\n\n    expect(books.find(book.id)).to eq(book)\n    expect(books.find(on_sale.id)).to be_nil\n  end\n\n  context \"raises a Hanami::Model::Error wrapped exception on\" do\n    it \"#create\" do\n      expect do\n        authors.create_with_books(name: \"Noam Chomsky\")\n      end.to raise_error Hanami::Model::Error\n    end\n\n    it \"#add\" do\n      author = authors.create(name: \"Machado de Assis\")\n      expect do\n        authors.add_book(author, title: \"O Alienista\", on_sale: nil)\n      end.to raise_error Hanami::Model::NotNullConstraintViolationError\n    end\n\n    # skipped spec\n    it \"#remove\"\n  end\nend\n"
  },
  {
    "path": "spec/integration/hanami/model/associations/has_one_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"spec_helper\"\n\nRSpec.describe \"Associations (has_one)\" do\n  extend PlatformHelpers\n\n  let(:users) { UserRepository.new }\n  let(:avatars) { AvatarRepository.new }\n\n  it \"returns nil if the association wasn't preloaded\" do\n    user       = users.create(name: \"John Doe\")\n    found      = users.find(user.id)\n\n    expect(found.avatar).to be_nil\n  end\n\n  it \"preloads the associated record\" do\n    user       = users.create(name: \"Baruch Spinoza\")\n    avatar     = avatars.create(user_id: user.id, url: \"http://www.notarealurl.com/avatar.png\")\n    found      = users.find_with_avatar(user.id)\n    expect(found).to eq(user)\n    expect(found.avatar).to eq(avatar)\n  end\n\n  it \"returns an Avatar\" do\n    user       = users.create(name: \"Simone de Beauvoir\")\n    avatar     = avatars.create(user_id: user.id, url: \"http://www.notarealurl.com/simone.png\")\n    found      = users.avatar_for(user)\n\n    expect(found).to eq(avatar)\n  end\n\n  it \"returns nil if the association was preloaded but no associated object is set\" do\n    user       = users.create(name: \"Henry Jenkins\")\n    found      = users.find_with_avatar(user.id)\n\n    expect(found).to eq(user)\n    expect(found.avatar).to be_nil\n  end\n\n  context \"#add\" do\n    it \"adds an an Avatar to an existing User\" do\n      user = users.create(name: \"Jean Paul-Sartre\")\n      avatar = users.add_avatar(user, url: \"http://www.notarealurl.com/sartre.png\")\n      found = users.find_with_avatar(user.id)\n\n      expect(found).to eq(user)\n      expect(found.avatar.id).to eq(avatar.id)\n      expect(found.avatar.url).to eq(\"http://www.notarealurl.com/sartre.png\")\n    end\n\n    it \"adds an an Avatar to an existing User when serializable data is received\" do\n      user = users.create(name: \"Jean Paul-Sartre\")\n      avatar = users.add_avatar(user, BaseParams.new(url: \"http://www.notarealurl.com/sartre.png\"))\n      found = users.find_with_avatar(user.id)\n\n      expect(found).to eq(user)\n      expect(found.avatar.id).to eq(avatar.id)\n      expect(found.avatar.url).to eq(\"http://www.notarealurl.com/sartre.png\")\n    end\n  end\n\n  context \"#update\" do\n    it \"updates the avatar\" do\n      user = users.create_with_avatar(name: \"Bakunin\", avatar: {url: \"bakunin.jpg\"})\n      users.update_avatar(user, url: url = \"http://history.com/bakunin.png\")\n\n      found = users.find_with_avatar(user.id)\n\n      expect(found).to eq(user)\n      expect(found.avatar).to eq(user.avatar)\n      expect(found.avatar.url).to eq(url)\n    end\n\n    it \"updates the avatar when serializable data is received\" do\n      user = users.create_with_avatar(name: \"Bakunin\", avatar: {url: \"bakunin.jpg\"})\n      users.update_avatar(user, BaseParams.new(url: url = \"http://history.com/bakunin.png\"))\n\n      found = users.find_with_avatar(user.id)\n\n      expect(found).to eq(user)\n      expect(found.avatar).to eq(user.avatar)\n      expect(found.avatar.url).to eq(url)\n    end\n  end\n\n  context \"#create\" do\n    it \"creates a User and an Avatar\" do\n      user = users.create_with_avatar(name: \"Lao Tse\", avatar: {url: \"http://lao-tse.io/me.jpg\"})\n      found = users.find_with_avatar(user.id)\n\n      expect(found.name).to eq(user.name)\n      expect(found.avatar).to eq(user.avatar)\n      expect(found.avatar.url).to eq(\"http://lao-tse.io/me.jpg\")\n    end\n\n    it \"creates a User and an Avatar when serializable data is received\" do\n      user = users.create_with_avatar(name: \"Lao Tse\", avatar: BaseParams.new(url: \"http://lao-tse.io/me.jpg\"))\n      found = users.find_with_avatar(user.id)\n\n      expect(found.name).to eq(user.name)\n      expect(found.avatar).to eq(user.avatar)\n      expect(found.avatar.url).to eq(\"http://lao-tse.io/me.jpg\")\n    end\n  end\n\n  context \"#delete\" do\n    it \"removes the Avatar\" do\n      user = users.create_with_avatar(name: \"Bob Ross\", avatar: {url: \"http://bobross/happy_little_avatar.jpg\"})\n      other = users.create_with_avatar(name: \"Candido Portinari\", avatar: {url: \"some_mugshot.jpg\"})\n      users.remove_avatar(user)\n      found = users.find_with_avatar(user.id)\n      other_found = users.find_with_avatar(other.id)\n\n      expect(found.avatar).to be_nil\n      expect(other_found.avatar).to be_an Avatar\n    end\n  end\n\n  context \"#replace\" do\n    it \"replaces the associated object\" do\n      user = users.create_with_avatar(name: \"Frank Herbert\", avatar: {url: \"http://not-real.com/avatar.jpg\"})\n      users.replace_avatar(user, url: \"http://totally-correct.com/avatar.jpg\")\n      found = users.find_with_avatar(user.id)\n\n      expect(found.avatar).to_not eq(user.avatar)\n\n      expect(avatars.by_user(user.id).size).to eq(1)\n    end\n\n    it \"replaces the associated object when serializable data is received\" do\n      user = users.create_with_avatar(name: \"Frank Herbert\", avatar: {url: \"http://not-real.com/avatar.jpg\"})\n      users.replace_avatar(user, BaseParams.new(url: \"http://totally-correct.com/avatar.jpg\"))\n      found = users.find_with_avatar(user.id)\n\n      expect(found.avatar).to_not eq(user.avatar)\n\n      expect(avatars.by_user(user.id).size).to eq(1)\n    end\n  end\n\n  context \"raises a Hanami::Model::Error wrapped exception on\" do\n    it \"#create\" do\n      expect do\n        users.create_with_avatar(name: \"Noam Chomsky\")\n      end.to raise_error Hanami::Model::Error\n    end\n\n    it \"#add\" do\n      user = users.create_with_avatar(name: \"Stephen Fry\", avatar: {url: \"fry_mugshot.png\"})\n      expect { users.add_avatar(user, url: \"new_mugshot.png\") }.to raise_error Hanami::Model::UniqueConstraintViolationError\n    end\n\n    # by default it seems that MySQL allows you to update a NOT NULL column to a NULL value\n    unless_platform(db: :mysql) do\n      it \"#update\" do\n        user = users.create_with_avatar(name: \"Dan North\", avatar: {url: \"bdd_creator.png\"})\n\n        expect do\n          users.update_avatar(user, url: nil)\n        end.to raise_error Hanami::Model::NotNullConstraintViolationError\n      end\n    end\n\n    it \"#replace\" do\n      user = users.create_with_avatar(name: \"Eric Evans\", avatar: {url: \"ddd_man.png\"})\n      expect { users.replace_avatar(user, url: nil) }.to raise_error Hanami::Model::NotNullConstraintViolationError\n    end\n  end\nend\n"
  },
  {
    "path": "spec/integration/hanami/model/associations/many_to_many_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe \"Associations (has_many :through)\" do\n  #### REPOS\n  let(:books) { BookRepository.new }\n  let(:categories) { CategoryRepository.new }\n  let(:ontologies) { BookOntologyRepository.new }\n\n  ### ENTITIES\n  let(:book) { books.create(title: \"Ontology: Encyclopedia of Database Systems\") }\n  let(:category) { categories.create(name: \"information science\") }\n\n  it \"returns nil if association wasn't preloaded\" do\n    found = books.find(book.id)\n    expect(found.categories).to be(nil)\n  end\n\n  it \"preloads the associated record\" do\n    ontologies.create(book_id: book.id, category_id: category.id)\n    found = books.find_with_categories(book.id)\n\n    expect(found).to eq(book)\n    expect(found.categories).to eq([category])\n  end\n\n  it \"returns an array of Categories\" do\n    ontologies.create(book_id: book.id, category_id: category.id)\n\n    found = books.categories_for(book)\n    expect(found).to eq([category])\n  end\n\n  it \"returns the count of on sale associated books\" do\n    on_sale = books.create(title: \"The Sense of Style\", on_sale: true)\n    ontologies.create(book_id: on_sale.id, category_id: category.id)\n\n    expect(categories.on_sales_books_count(category)).to eq(1)\n  end\n\n  context \"#add\" do\n    it \"adds an object to the collection\" do\n      books.add_category(book, category)\n\n      found_book = books.find_with_categories(book.id)\n      found_category = categories.find_with_books(category.id)\n\n      expect(found_book).to eq(book)\n      expect(found_book.categories).to eq([category])\n      expect(found_category).to eq(category)\n      expect(found_category.books).to eq([book])\n    end\n\n    it \"associates a collection of records\" do\n      other_book = books.create(title: \"Ontological Engineering\")\n      categories.add_books(category, book, other_book)\n      found = categories.find_with_books(category.id)\n\n      expect(found.books).to match_array([book, other_book])\n    end\n  end\n\n  context \"#delete\" do\n    it \"removes all association information\" do\n      books.add_category(book, category)\n      categorized = books.find_with_categories(book.id)\n      books.clear_categories(book)\n      found = books.find_with_categories(book.id)\n\n      expect(categorized.categories).to be_an Array\n      expect(categorized.categories).to match_array([category])\n      expect(found.categories).to be_empty\n      expect(found).to eq(categorized)\n    end\n\n    it \"does not touch other books\" do\n      other_book = books.create(title: \"Do not meddle with\")\n      books.add_category(other_book, category)\n      books.add_category(book, category)\n\n      books.clear_categories(book)\n      found = books.find_with_categories(book.id)\n      other_found = books.find_with_categories(other_book.id)\n\n      expect(found).to eq(book)\n      expect(other_book).to eq(other_found)\n      expect(other_found.categories).to eq([category])\n      expect(found.categories).to be_empty\n    end\n  end\n\n  context \"#remove\" do\n    it \"removes the desired association\" do\n      to_remove = books.create(title: \"The Life of a Stoic\")\n      books.add_category(to_remove, category)\n\n      categories.remove_book(category, to_remove.id)\n      found = categories.find_with_books(category.id)\n\n      expect(found.books).to_not include(to_remove)\n    end\n  end\n\n  context \"collection methods\" do\n    it \"returns an array of books\" do\n      ontologies.create(book_id: book.id, category_id: category.id)\n\n      actual = categories.books_for(category).to_a\n      expect(actual).to eq([book])\n    end\n\n    it \"iterates through the categories\" do\n      ontologies.create(book_id: book.id, category_id: category.id)\n      actual = []\n\n      categories.books_for(category).each do |book|\n        expect(book).to be_an_instance_of(Book)\n        actual << book\n      end\n\n      expect(actual).to eq([book])\n    end\n\n    it \"iterates through the books and returns an array\" do\n      ontologies.create(book_id: book.id, category_id: category.id)\n\n      actual = categories.books_for(category).map(&:id)\n      expect(actual).to eq([book.id])\n    end\n\n    it \"returns the count of the associated books\" do\n      other_book = books.create(title: \"Practical Ontologies for Information Professionals\")\n      ontologies.create(book_id: book.id, category_id: category.id)\n      ontologies.create(book_id: other_book.id, category_id: category.id)\n\n      expect(categories.books_count(category)).to eq(2)\n    end\n  end\n\n  context \"raises a Hanami::Model::Error wrapped exception on\" do\n    it \"#add\" do\n      expect do\n        categories.add_books(category, id: -2)\n      end.to raise_error Hanami::Model::ForeignKeyConstraintViolationError\n    end\n  end\nend\n"
  },
  {
    "path": "spec/integration/hanami/model/associations/relation_alias_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe \"Alias (:as)  support for associations\" do\n  let(:users) { UserRepository.new }\n  let(:posts) { PostRepository.new }\n  let(:comments) { CommentRepository.new }\n\n  it \"the attribute is named after the association\" do\n    user = users.create(name: \"Jules Verne\")\n    post = posts.create(title: \"World Traveling made easy\", user_id: user.id)\n\n    post_found = posts.find_with_author(post.id)\n    expect(post_found.author).to eq(user)\n\n    user_found = users.find_with_threads(user.id)\n    expect(user_found.threads).to match_array([post])\n  end\n\n  it \"it works with nested aggregates\" do\n    user = users.create(name: \"Jules Verne\")\n    post = posts.create(title: \"World Traveling made easy\", user_id: user.id)\n    commenter = users.create(name: \"Thomas Reid\")\n    comments.create(user_id: commenter.id, post_id: post.id)\n\n    found = posts.feed_for(post.id)\n    expect(found.author).to eq(user)\n    expect(found.comments[0].user).to eq(commenter)\n  end\n\n  context \"#assoc support (calling assoc by the alias)\" do\n    it \"for #belongs_to\" do\n      user = users.create(name: \"Jules Verne\")\n      post = posts.create(title: \"World Traveling made easy\", user_id: user.id)\n      commenter = users.create(name: \"Thomas Reid\")\n      comment = comments.create(user_id: commenter.id, post_id: post.id)\n\n      found_author = posts.author_for(post)\n      expect(found_author).to eq(user)\n\n      found_commenter = comments.commenter_for(comment)\n      expect(found_commenter).to eq(commenter)\n    end\n\n    it \"for #has_many\" do\n      user = users.create(name: \"Jules Verne\")\n      post = posts.create(title: \"World Traveling made easy\", user_id: user.id)\n\n      found_threads = users.threads_for(user)\n      expect(found_threads).to match_array [post]\n    end\n\n    it \"for #has_many :through\" do\n      user = users.create(name: \"Jules Verne\")\n      post = posts.create(title: \"World Traveling made easy\", user_id: user.id)\n      commenter = users.create(name: \"Thomas Reid\")\n      comments.create(user_id: commenter.id, post_id: post.id)\n\n      commenters = posts.commenters_for(post)\n\n      expect(commenters).to match_array([commenter])\n    end\n  end\nend\n"
  },
  {
    "path": "spec/integration/hanami/model/migration/mysql.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.shared_examples \"migration_integration_mysql\" do\n  before do\n    @schema = Pathname.new(\"#{__dir__}/../../../../../tmp/schema.sql\").expand_path\n    @connection = Sequel.connect(ENV[\"HANAMI_DATABASE_URL\"])\n\n    Hanami::Model::Migrator::Adapter.for(Hanami::Model.configuration).dump\n  end\n\n  describe \"columns\" do\n    it \"defines column types\" do\n      table = @connection.schema(:column_types)\n\n      name, options = table[0]\n      expect(name).to eq(:integer1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"int\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[1]\n      expect(name).to eq(:integer2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"int\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[2]\n      expect(name).to eq(:integer3)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"int\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[3]\n      expect(name).to eq(:string1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"varchar(255)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[4]\n      expect(name).to eq(:string2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"varchar(3)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[5]\n      expect(name).to eq(:string5)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"varchar(50)\")\n      expect(options.fetch(:max_length)).to eq(50)\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[6]\n      expect(name).to eq(:string6)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"char(255)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[7]\n      expect(name).to eq(:string7)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"char(64)\")\n      expect(options.fetch(:max_length)).to eq(64)\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[8]\n      expect(name).to eq(:string8)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"text\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[9]\n      expect(name).to eq(:file1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:blob)\n      expect(options.fetch(:db_type)).to eq(\"blob\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[10]\n      expect(name).to eq(:file2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:blob)\n      expect(options.fetch(:db_type)).to eq(\"blob\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[11]\n      expect(name).to eq(:number1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"int\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[12]\n      expect(name).to eq(:number2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"bigint\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[13]\n      expect(name).to eq(:number3)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:float)\n      expect(options.fetch(:db_type)).to eq(\"double\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[14]\n      expect(name).to eq(:number4)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"decimal(10,0)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[15]\n      expect(name).to eq(:number5)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      # expect(options.fetch(:type)).to eq(:decimal)\n      expect(options.fetch(:db_type)).to eq(\"decimal(10,0)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[16]\n      expect(name).to eq(:number6)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:decimal)\n      expect(options.fetch(:db_type)).to eq(\"decimal(10,2)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[17]\n      expect(name).to eq(:number7)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"decimal(10,0)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[18]\n      expect(name).to eq(:date1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:date)\n      expect(options.fetch(:db_type)).to eq(\"date\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[19]\n      expect(name).to eq(:date2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:datetime)\n      expect(options.fetch(:db_type)).to eq(\"datetime\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[20]\n      expect(name).to eq(:time1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:datetime)\n      expect(options.fetch(:db_type)).to eq(\"datetime\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[21]\n      expect(name).to eq(:time2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:time)\n      expect(options.fetch(:db_type)).to eq(\"time\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[22]\n      expect(name).to eq(:boolean1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:boolean)\n      expect(options.fetch(:db_type)).to eq(\"tinyint(1)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[23]\n      expect(name).to eq(:boolean2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:boolean)\n      expect(options.fetch(:db_type)).to eq(\"tinyint(1)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n    end\n\n    it \"defines column defaults\" do\n      table = @connection.schema(:default_values)\n\n      name, options = table[0]\n      expect(name).to eq(:a)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"23\")\n      expect(options.fetch(:ruby_default)).to eq(23)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"int\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[1]\n      expect(name).to eq(:b)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"Hanami\")\n      expect(options.fetch(:ruby_default)).to eq(\"Hanami\")\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"varchar(255)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[2]\n      expect(name).to eq(:c)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"-1\")\n      expect(options.fetch(:ruby_default)).to eq(-1)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"int\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[3]\n      expect(name).to eq(:d)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"0\")\n      expect(options.fetch(:ruby_default)).to eq(0)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"bigint\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[4]\n      expect(name).to eq(:e)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"3.14\")\n      expect(options.fetch(:ruby_default)).to eq(3.14)\n      expect(options.fetch(:type)).to eq(:float)\n      expect(options.fetch(:db_type)).to eq(\"double\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[5]\n      expect(name).to eq(:f)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"1\")\n      expect(options.fetch(:ruby_default)).to eq(1.0)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"decimal(10,0)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[6]\n      expect(name).to eq(:g)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"943943\")\n      expect(options.fetch(:ruby_default)).to eq(943_943)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"decimal(10,0)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[10]\n      expect(name).to eq(:k)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"1\")\n      expect(options.fetch(:ruby_default)).to eq(true)\n      expect(options.fetch(:type)).to eq(:boolean)\n      expect(options.fetch(:db_type)).to eq(\"tinyint(1)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[11]\n      expect(name).to eq(:l)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"0\")\n      expect(options.fetch(:ruby_default)).to eq(false)\n      expect(options.fetch(:type)).to eq(:boolean)\n      expect(options.fetch(:db_type)).to eq(\"tinyint(1)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n    end\n\n    it \"defines null constraint\" do\n      table = @connection.schema(:null_constraints)\n\n      name, options = table[0]\n      expect(name).to eq(:a)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n\n      name, options = table[1]\n      expect(name).to eq(:b)\n\n      expect(options.fetch(:allow_null)).to eq(false)\n\n      name, options = table[2]\n      expect(name).to eq(:c)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n    end\n\n    it \"defines column index\" do\n      indexes = @connection.indexes(:column_indexes)\n\n      expect(indexes.fetch(:column_indexes_a_index, nil)).to be_nil\n      expect(indexes.fetch(:column_indexes_b_index, nil)).to be_nil\n\n      index = indexes.fetch(:column_indexes_c_index)\n      expect(index[:unique]).to eq(false)\n      expect(index[:columns]).to eq([:c])\n    end\n\n    it \"defines index via #index\" do\n      indexes = @connection.indexes(:column_indexes)\n\n      index = indexes.fetch(:column_indexes_d_index)\n      expect(index[:unique]).to eq(true)\n      expect(index[:columns]).to eq([:d])\n\n      index = indexes.fetch(:column_indexes_b_c_index)\n      expect(index[:unique]).to eq(false)\n      expect(index[:columns]).to eq(%i[b c])\n\n      index = indexes.fetch(:column_indexes_coords_index)\n      expect(index[:unique]).to eq(false)\n      expect(index[:columns]).to eq(%i[lat lng])\n    end\n\n    it \"defines primary key (via #primary_key :id)\" do\n      table = @connection.schema(:primary_keys_1)\n\n      name, options = table[0]\n      expect(name).to eq(:id)\n\n      expect(options.fetch(:allow_null)).to eq(false)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"int\")\n      expect(options.fetch(:primary_key)).to eq(true)\n      expect(options.fetch(:auto_increment)).to eq(true)\n    end\n\n    it \"defines composite primary key (via #primary_key [:column1, :column2])\" do\n      table = @connection.schema(:primary_keys_3)\n\n      name, options = table[0]\n      expect(name).to eq(:group_id)\n\n      expect(options.fetch(:allow_null)).to eq(false)\n\n      expected = Platform.match do\n        default { nil }\n      end\n\n      expect(options.fetch(:default)).to eq(expected)\n\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"int\")\n      expect(options.fetch(:primary_key)).to eq(true)\n      expect(options.fetch(:auto_increment)).to eq(false)\n\n      name, options = table[1]\n      expect(name).to eq(:position)\n\n      expect(options.fetch(:allow_null)).to eq(false)\n\n      expected = Platform.match do\n        default { nil }\n      end\n\n      expect(options.fetch(:default)).to eq(expected)\n\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"int\")\n      expect(options.fetch(:primary_key)).to eq(true)\n      expect(options.fetch(:auto_increment)).to eq(false)\n    end\n\n    it \"defines primary key (via #column primary_key: true)\" do\n      table = @connection.schema(:primary_keys_2)\n\n      name, options = table[0]\n      expect(name).to eq(:name)\n\n      expect(options.fetch(:allow_null)).to eq(false)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"varchar(255)\")\n      expect(options.fetch(:primary_key)).to eq(true)\n      expect(options.fetch(:auto_increment)).to eq(false)\n    end\n\n    it \"defines foreign key (via #foreign_key)\" do\n      table = @connection.schema(:albums)\n\n      name, options = table[1]\n      expect(name).to eq(:artist_id)\n\n      expect(options.fetch(:allow_null)).to eq(false)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"int\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      foreign_key = @connection.foreign_key_list(:albums).first\n      expect(foreign_key.fetch(:columns)).to eq([:artist_id])\n      expect(foreign_key.fetch(:table)).to eq(:artists)\n      expect(foreign_key.fetch(:key)).to eq([:id])\n      # expect(foreign_key.fetch(:on_update)).to eq(:no_action)\n      # expect(foreign_key.fetch(:on_delete)).to eq(:cascade)\n    end\n\n    it \"defines column constraint and check\"\n    # it 'defines column constraint and check' do\n    #   expect(@schema.read).to include %(CREATE TABLE `table_constraints` (`age` integer, `role` varchar(255), CONSTRAINT `age_constraint` CHECK (`age` > 18), CHECK (role IN(\"contributor\", \"manager\", \"owner\")));)\n    # end\n  end\nend\n"
  },
  {
    "path": "spec/integration/hanami/model/migration/postgresql.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.shared_examples \"migration_integration_postgresql\" do\n  before do\n    @schema = Pathname.new(\"#{__dir__}/../../../../../tmp/schema.sql\").expand_path\n    @connection = Sequel.connect(ENV[\"HANAMI_DATABASE_URL\"])\n\n    Hanami::Model::Migrator::Adapter.for(Hanami::Model.configuration).dump\n  end\n\n  describe \"columns\" do\n    it \"defines column types\" do\n      table = @connection.schema(:column_types)\n\n      name, options = table[0]\n      expect(name).to eq(:integer1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"integer\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[1]\n      expect(name).to eq(:integer2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"integer\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[2]\n      expect(name).to eq(:integer3)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"integer\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[3]\n      expect(name).to eq(:string1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"text\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[4]\n      expect(name).to eq(:string2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"text\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[5]\n      expect(name).to eq(:string3)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"character varying(1)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[6]\n      expect(name).to eq(:string4)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"character varying(2)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[7]\n      expect(name).to eq(:string5)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"character(3)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[8]\n      expect(name).to eq(:string6)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"character(4)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[9]\n      expect(name).to eq(:string7)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"character varying(50)\")\n      expect(options.fetch(:max_length)).to eq(50)\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[10]\n      expect(name).to eq(:string8)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"character(255)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[11]\n      expect(name).to eq(:string9)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"character(64)\")\n      expect(options.fetch(:max_length)).to eq(64)\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[12]\n      expect(name).to eq(:string10)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"text\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[13]\n      expect(name).to eq(:file1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:blob)\n      expect(options.fetch(:db_type)).to eq(\"bytea\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[14]\n      expect(name).to eq(:file2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:blob)\n      expect(options.fetch(:db_type)).to eq(\"bytea\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[15]\n      expect(name).to eq(:number1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"integer\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[16]\n      expect(name).to eq(:number2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"bigint\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[17]\n      expect(name).to eq(:number3)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:float)\n      expect(options.fetch(:db_type)).to eq(\"double precision\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[18]\n      expect(name).to eq(:number4)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:decimal)\n      expect(options.fetch(:db_type)).to eq(\"numeric\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[19]\n      expect(name).to eq(:number5)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      # expect(options.fetch(:type)).to eq(:decimal)\n      expect(options.fetch(:db_type)).to eq(\"numeric(10,0)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[20]\n      expect(name).to eq(:number6)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:decimal)\n      expect(options.fetch(:db_type)).to eq(\"numeric(10,2)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[21]\n      expect(name).to eq(:number7)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:decimal)\n      expect(options.fetch(:db_type)).to eq(\"numeric\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[22]\n      expect(name).to eq(:date1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:date)\n      expect(options.fetch(:db_type)).to eq(\"date\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[23]\n      expect(name).to eq(:date2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:datetime)\n      expect(options.fetch(:db_type)).to eq(\"timestamp without time zone\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[24]\n      expect(name).to eq(:time1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:datetime)\n      expect(options.fetch(:db_type)).to eq(\"timestamp without time zone\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[25]\n      expect(name).to eq(:time2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:time)\n      expect(options.fetch(:db_type)).to eq(\"time without time zone\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[26]\n      expect(name).to eq(:boolean1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:boolean)\n      expect(options.fetch(:db_type)).to eq(\"boolean\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[27]\n      expect(name).to eq(:boolean2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:boolean)\n      expect(options.fetch(:db_type)).to eq(\"boolean\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[28]\n      expect(name).to eq(:array1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"integer[]\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[29]\n      expect(name).to eq(:array2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"integer[]\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[30]\n      expect(name).to eq(:array3)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"text[]\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[31]\n      expect(name).to eq(:money1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      # expect(options.fetch(:type)).to eq(:money)\n      expect(options.fetch(:db_type)).to eq(\"money\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[32]\n      expect(name).to eq(:enum1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      # expect(options.fetch(:type)).to eq(:mood)\n      expect(options.fetch(:db_type)).to eq(\"mood\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[33]\n      expect(name).to eq(:geometric1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      # expect(options.fetch(:type)).to eq(:point)\n      expect(options.fetch(:db_type)).to eq(\"point\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[34]\n      expect(name).to eq(:geometric2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      # expect(options.fetch(:type)).to eq(:line)\n      expect(options.fetch(:db_type)).to eq(\"line\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[35]\n      expect(name).to eq(:geometric3)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"'<(15,15),1>'::circle\")\n      # expect(options.fetch(:type)).to eq(:circle)\n      expect(options.fetch(:db_type)).to eq(\"circle\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[36]\n      expect(name).to eq(:net1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"'192.168.0.0/24'::cidr\")\n      # expect(options.fetch(:type)).to eq(:cidr)\n      expect(options.fetch(:db_type)).to eq(\"cidr\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[37]\n      expect(name).to eq(:uuid1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"uuid_generate_v4()\")\n      # expect(options.fetch(:type)).to eq(:uuid)\n      expect(options.fetch(:db_type)).to eq(\"uuid\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[38]\n      expect(name).to eq(:xml1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      # expect(options.fetch(:type)).to eq(:xml)\n      expect(options.fetch(:db_type)).to eq(\"xml\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[39]\n      expect(name).to eq(:json1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      # expect(options.fetch(:type)).to eq(:json)\n      expect(options.fetch(:db_type)).to eq(\"json\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[40]\n      expect(name).to eq(:json2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      # expect(options.fetch(:type)).to eq(:jsonb)\n      expect(options.fetch(:db_type)).to eq(\"jsonb\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[41]\n      expect(name).to eq(:composite1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"ROW('fuzzy dice'::text, 42, 1.99)\")\n      # expect(options.fetch(:type)).to eq(:inventory_item)\n      expect(options.fetch(:db_type)).to eq(\"inventory_item\")\n      expect(options.fetch(:primary_key)).to eq(false)\n    end\n\n    it \"defines column defaults\" do\n      table = @connection.schema(:default_values)\n\n      name, options = table[0]\n      expect(name).to eq(:a)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"23\")\n      expect(options.fetch(:ruby_default)).to eq(23)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"integer\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[1]\n      expect(name).to eq(:b)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"'Hanami'::text\")\n      expect(options.fetch(:ruby_default)).to eq(\"Hanami\")\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"text\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[2]\n      expect(name).to eq(:c)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n\n      expected = Platform.match do\n        default { \"'-1'::integer\" }\n      end\n\n      expect(options.fetch(:default)).to eq(expected)\n\n      # expect(options.fetch(:ruby_default)).to eq(-1)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"integer\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[3]\n      expect(name).to eq(:d)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"0\")\n      expect(options.fetch(:ruby_default)).to eq(0)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"bigint\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[4]\n      expect(name).to eq(:e)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"3.14\")\n      expect(options.fetch(:ruby_default)).to eq(3.14)\n      expect(options.fetch(:type)).to eq(:float)\n      expect(options.fetch(:db_type)).to eq(\"double precision\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[5]\n      expect(name).to eq(:f)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"1.0\")\n      expect(options.fetch(:ruby_default)).to eq(1.0)\n      expect(options.fetch(:type)).to eq(:decimal)\n      expect(options.fetch(:db_type)).to eq(\"numeric\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[6]\n      expect(name).to eq(:g)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"943943\")\n      expect(options.fetch(:ruby_default)).to eq(943_943)\n      expect(options.fetch(:type)).to eq(:decimal)\n      expect(options.fetch(:db_type)).to eq(\"numeric\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[10]\n      expect(name).to eq(:k)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"true\")\n      expect(options.fetch(:ruby_default)).to eq(true)\n      expect(options.fetch(:type)).to eq(:boolean)\n      expect(options.fetch(:db_type)).to eq(\"boolean\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[11]\n      expect(name).to eq(:l)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"false\")\n      expect(options.fetch(:ruby_default)).to eq(false)\n      expect(options.fetch(:type)).to eq(:boolean)\n      expect(options.fetch(:db_type)).to eq(\"boolean\")\n      expect(options.fetch(:primary_key)).to eq(false)\n    end\n\n    it \"defines null constraint\" do\n      table = @connection.schema(:null_constraints)\n\n      name, options = table[0]\n      expect(name).to eq(:a)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n\n      name, options = table[1]\n      expect(name).to eq(:b)\n\n      expect(options.fetch(:allow_null)).to eq(false)\n\n      name, options = table[2]\n      expect(name).to eq(:c)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n    end\n\n    it \"defines column index\" do\n      indexes = @connection.indexes(:column_indexes)\n\n      expect(indexes.fetch(:column_indexes_a_index, nil)).to be_nil\n      expect(indexes.fetch(:column_indexes_b_index, nil)).to be_nil\n\n      index = indexes.fetch(:column_indexes_c_index)\n      expect(index[:unique]).to eq(false)\n      expect(index[:columns]).to eq([:c])\n    end\n\n    it \"defines index via #index\" do\n      indexes = @connection.indexes(:column_indexes)\n\n      index = indexes.fetch(:column_indexes_d_index)\n      expect(index[:unique]).to eq(true)\n      expect(index[:columns]).to eq([:d])\n\n      index = indexes.fetch(:column_indexes_b_c_index)\n      expect(index[:unique]).to eq(false)\n      expect(index[:columns]).to eq(%i[b c])\n\n      index = indexes.fetch(:column_indexes_coords_index)\n      expect(index[:unique]).to eq(false)\n      expect(index[:columns]).to eq(%i[lat lng])\n    end\n\n    it \"defines primary key (via #primary_key :id)\" do\n      table = @connection.schema(:primary_keys_1)\n\n      name, options = table[0]\n      expect(name).to eq(:id)\n\n      expect(options.fetch(:allow_null)).to eq(false)\n      expect(options.fetch(:default)).to eq(\"nextval('primary_keys_1_id_seq'::regclass)\")\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"integer\")\n      expect(options.fetch(:primary_key)).to eq(true)\n      expect(options.fetch(:auto_increment)).to eq(true)\n    end\n\n    it \"defines composite primary key (via #primary_key [:column1, :column2])\" do\n      table = @connection.schema(:primary_keys_3)\n\n      name, options = table[0]\n      expect(name).to eq(:group_id)\n\n      expect(options.fetch(:allow_null)).to eq(false)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"integer\")\n      expect(options.fetch(:primary_key)).to eq(true)\n      expect(options.fetch(:auto_increment)).to eq(false)\n\n      name, options = table[1]\n      expect(name).to eq(:position)\n\n      expect(options.fetch(:allow_null)).to eq(false)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"integer\")\n      expect(options.fetch(:primary_key)).to eq(true)\n      expect(options.fetch(:auto_increment)).to eq(false)\n    end\n\n    it \"defines primary key (via #column primary_key: true)\" do\n      table = @connection.schema(:primary_keys_2)\n\n      name, options = table[0]\n      expect(name).to eq(:name)\n\n      expect(options.fetch(:allow_null)).to eq(false)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"text\")\n      expect(options.fetch(:primary_key)).to eq(true)\n      expect(options.fetch(:auto_increment)).to eq(false)\n    end\n\n    it \"defines foreign key (via #foreign_key)\" do\n      table = @connection.schema(:albums)\n\n      name, options = table[1]\n      expect(name).to eq(:artist_id)\n\n      expect(options.fetch(:allow_null)).to eq(false)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"integer\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      foreign_key = @connection.foreign_key_list(:albums).first\n      expect(foreign_key.fetch(:columns)).to eq([:artist_id])\n      expect(foreign_key.fetch(:table)).to eq(:artists)\n      expect(foreign_key.fetch(:key)).to eq([:id])\n      expect(foreign_key.fetch(:on_update)).to eq(:no_action)\n      expect(foreign_key.fetch(:on_delete)).to eq(:cascade)\n    end\n\n    unless Platform.ci?\n      it \"defines column constraint and check\" do\n        actual = @schema.read\n\n        expect(actual).to include %(CONSTRAINT age_constraint CHECK ((age > 18)))\n        expect(actual).to include %(CONSTRAINT table_constraints_role_check CHECK ((role = ANY (ARRAY['contributor'::text, 'manager'::text, 'owner'::text]))))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/integration/hanami/model/migration/sqlite.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.shared_examples \"migration_integration_sqlite\" do\n  before do\n    @schema = Pathname.new(\"#{__dir__}/../../../../../tmp/schema.sql\").expand_path\n    @connection = Sequel.connect(ENV[\"HANAMI_DATABASE_URL\"])\n\n    Hanami::Model::Migrator::Adapter.for(Hanami::Model.configuration).dump\n  end\n\n  describe \"columns\" do\n    it \"defines column types\" do\n      table = @connection.schema(:column_types)\n\n      name, options = table[0]\n      expect(name).to eq(:integer1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"integer\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[1]\n      expect(name).to eq(:integer2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"integer\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[2]\n      expect(name).to eq(:integer3)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"integer\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[3]\n      expect(name).to eq(:string1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"varchar(255)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[4]\n      expect(name).to eq(:string2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"string\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[5]\n      expect(name).to eq(:string3)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"string\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[6]\n      expect(name).to eq(:string4)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"varchar(3)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[7]\n      expect(name).to eq(:string5)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"varchar(50)\")\n      expect(options.fetch(:max_length)).to eq(50)\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[8]\n      expect(name).to eq(:string6)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"char(255)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[9]\n      expect(name).to eq(:string7)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"char(64)\")\n      expect(options.fetch(:max_length)).to eq(64)\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[10]\n      expect(name).to eq(:string8)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"text\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[11]\n      expect(name).to eq(:file1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:blob)\n      expect(options.fetch(:db_type)).to eq(\"blob\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[12]\n      expect(name).to eq(:file2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:blob)\n      expect(options.fetch(:db_type)).to eq(\"blob\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[13]\n      expect(name).to eq(:number1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"integer\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[14]\n      expect(name).to eq(:number2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"bigint\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[15]\n      expect(name).to eq(:number3)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:float)\n      expect(options.fetch(:db_type)).to eq(\"double precision\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[16]\n      expect(name).to eq(:number4)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:decimal)\n      expect(options.fetch(:db_type)).to eq(\"numeric\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[17]\n      expect(name).to eq(:number5)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      # expect(options.fetch(:type)).to eq(:decimal)\n      expect(options.fetch(:db_type)).to eq(\"numeric(10)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[18]\n      expect(name).to eq(:number6)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:decimal)\n      expect(options.fetch(:db_type)).to eq(\"numeric(10, 2)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[19]\n      expect(name).to eq(:number7)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:decimal)\n      expect(options.fetch(:db_type)).to eq(\"numeric\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[20]\n      expect(name).to eq(:date1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:date)\n      expect(options.fetch(:db_type)).to eq(\"date\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[21]\n      expect(name).to eq(:date2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:datetime)\n      expect(options.fetch(:db_type)).to eq(\"timestamp\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[22]\n      expect(name).to eq(:time1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:datetime)\n      expect(options.fetch(:db_type)).to eq(\"timestamp\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[23]\n      expect(name).to eq(:time2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:time)\n      expect(options.fetch(:db_type)).to eq(\"time\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[24]\n      expect(name).to eq(:boolean1)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:boolean)\n      expect(options.fetch(:db_type)).to eq(\"boolean\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[25]\n      expect(name).to eq(:boolean2)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:boolean)\n      expect(options.fetch(:db_type)).to eq(\"boolean\")\n      expect(options.fetch(:primary_key)).to eq(false)\n    end\n\n    it \"defines column defaults\" do\n      table = @connection.schema(:default_values)\n\n      name, options = table[0]\n      expect(name).to eq(:a)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"23\")\n      expect(options.fetch(:ruby_default)).to eq(23)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"integer\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[1]\n      expect(name).to eq(:b)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"'Hanami'\")\n      expect(options.fetch(:ruby_default)).to eq(\"Hanami\")\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"varchar(255)\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[2]\n      expect(name).to eq(:c)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"-1\")\n      expect(options.fetch(:ruby_default)).to eq(-1)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"integer\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[3]\n      expect(name).to eq(:d)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"0\")\n      expect(options.fetch(:ruby_default)).to eq(0)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"bigint\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[4]\n      expect(name).to eq(:e)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"3.14\")\n      expect(options.fetch(:ruby_default)).to eq(3.14)\n      expect(options.fetch(:type)).to eq(:float)\n      expect(options.fetch(:db_type)).to eq(\"double precision\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[5]\n      expect(name).to eq(:f)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"1.0\")\n      expect(options.fetch(:ruby_default)).to eq(1.0)\n      expect(options.fetch(:type)).to eq(:decimal)\n      expect(options.fetch(:db_type)).to eq(\"numeric\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[6]\n      expect(name).to eq(:g)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"943943\")\n      expect(options.fetch(:ruby_default)).to eq(943_943)\n      expect(options.fetch(:type)).to eq(:decimal)\n      expect(options.fetch(:db_type)).to eq(\"numeric\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[10]\n      expect(name).to eq(:k)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"1\")\n      expect(options.fetch(:ruby_default)).to eq(true)\n      expect(options.fetch(:type)).to eq(:boolean)\n      expect(options.fetch(:db_type)).to eq(\"boolean\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      name, options = table[11]\n      expect(name).to eq(:l)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n      expect(options.fetch(:default)).to eq(\"0\")\n      expect(options.fetch(:ruby_default)).to eq(false)\n      expect(options.fetch(:type)).to eq(:boolean)\n      expect(options.fetch(:db_type)).to eq(\"boolean\")\n      expect(options.fetch(:primary_key)).to eq(false)\n    end\n\n    it \"defines null constraint\" do\n      table = @connection.schema(:null_constraints)\n\n      name, options = table[0]\n      expect(name).to eq(:a)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n\n      name, options = table[1]\n      expect(name).to eq(:b)\n\n      expect(options.fetch(:allow_null)).to eq(false)\n\n      name, options = table[2]\n      expect(name).to eq(:c)\n\n      expect(options.fetch(:allow_null)).to eq(true)\n    end\n\n    it \"defines column index\" do\n      indexes = @connection.indexes(:column_indexes)\n\n      expect(indexes.fetch(:column_indexes_a_index, nil)).to be_nil\n      expect(indexes.fetch(:column_indexes_b_index, nil)).to be_nil\n\n      index = indexes.fetch(:column_indexes_c_index)\n      expect(index[:unique]).to eq(false)\n      expect(index[:columns]).to eq([:c])\n    end\n\n    it \"defines index via #index\" do\n      indexes = @connection.indexes(:column_indexes)\n\n      index = indexes.fetch(:column_indexes_d_index)\n      expect(index[:unique]).to eq(true)\n      expect(index[:columns]).to eq([:d])\n\n      index = indexes.fetch(:column_indexes_b_c_index)\n      expect(index[:unique]).to eq(false)\n      expect(index[:columns]).to eq(%i[b c])\n\n      index = indexes.fetch(:column_indexes_coords_index)\n      expect(index[:unique]).to eq(false)\n      expect(index[:columns]).to eq(%i[lat lng])\n    end\n\n    it \"defines primary key (via #primary_key :id)\" do\n      table = @connection.schema(:primary_keys_1)\n\n      name, options = table[0]\n      expect(name).to eq(:id)\n\n      expect(options.fetch(:allow_null)).to eq(false)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"integer\")\n      expect(options.fetch(:primary_key)).to eq(true)\n      expect(options.fetch(:auto_increment)).to eq(true)\n    end\n\n    it \"defines composite primary key (via #primary_key [:column1, :column2])\" do\n      table = @connection.schema(:primary_keys_3)\n\n      name, options = table[0]\n      expect(name).to eq(:group_id)\n\n      expect(options.fetch(:allow_null)).to eq(false)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"integer\")\n      expect(options.fetch(:primary_key)).to eq(true)\n      expect(options.fetch(:auto_increment)).to eq(false)\n\n      name, options = table[1]\n      expect(name).to eq(:position)\n\n      expect(options.fetch(:allow_null)).to eq(false)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"integer\")\n      expect(options.fetch(:primary_key)).to eq(true)\n      expect(options.fetch(:auto_increment)).to eq(false)\n    end\n\n    it \"defines primary key (via #column primary_key: true)\" do\n      table = @connection.schema(:primary_keys_2)\n\n      name, options = table[0]\n      expect(name).to eq(:name)\n\n      expect(options.fetch(:allow_null)).to eq(false)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:string)\n      expect(options.fetch(:db_type)).to eq(\"varchar(255)\")\n      expect(options.fetch(:primary_key)).to eq(true)\n      expect(options.fetch(:auto_increment)).to eq(false)\n    end\n\n    it \"defines foreign key (via #foreign_key)\" do\n      table = @connection.schema(:albums)\n\n      name, options = table[1]\n      expect(name).to eq(:artist_id)\n\n      expect(options.fetch(:allow_null)).to eq(false)\n      expect(options.fetch(:default)).to eq(nil)\n      expect(options.fetch(:type)).to eq(:integer)\n      expect(options.fetch(:db_type)).to eq(\"integer\")\n      expect(options.fetch(:primary_key)).to eq(false)\n\n      foreign_key = @connection.foreign_key_list(:albums).first\n      expect(foreign_key.fetch(:columns)).to eq([:artist_id])\n      expect(foreign_key.fetch(:table)).to eq(:artists)\n      expect(foreign_key.fetch(:key)).to eq(nil)\n      expect(foreign_key.fetch(:on_update)).to eq(:no_action)\n      expect(foreign_key.fetch(:on_delete)).to eq(:cascade)\n    end\n\n    it \"defines column constraint and check\" do\n      expect(@schema.read).to include %(CREATE TABLE `table_constraints` (`age` integer, `role` varchar(255), CONSTRAINT `age_constraint` CHECK (`age` > 18), CHECK (role IN(\"contributor\", \"manager\", \"owner\")));)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/integration/hanami/model/migration_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative \"./migration/#{Database.engine}.rb\"\n\nRSpec.describe \"Hanami::Model.migration\" do\n  include_examples \"migration_integration_#{Database.engine}\"\nend\n"
  },
  {
    "path": "spec/integration/hanami/model/repository/base_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"securerandom\"\n\nRSpec.describe \"Repository (base)\" do\n  extend PlatformHelpers\n\n  describe \"#find\" do\n    it \"finds record by primary key\" do\n      repository = UserRepository.new\n      user = repository.create(name: \"L\")\n      found = repository.find(user.id)\n\n      expect(found).to eq(user)\n    end\n\n    it \"returns nil when nil is given\" do\n      repository = UserRepository.new\n      repository.create(name: \"L\")\n      found = repository.find(nil)\n\n      expect(found).to be_nil\n    end\n\n    it \"returns nil for missing record\" do\n      repository = UserRepository.new\n      found = repository.find(\"9999999\")\n\n      expect(found).to be_nil\n    end\n\n    # See https://github.com/hanami/model/issues/374\n    describe \"with non-autoincrement primary key\" do\n      before do\n        repository.clear\n      end\n\n      let(:repository) { LabelRepository.new }\n      let(:id)         { 1 }\n\n      it \"raises error\" do\n        repository.create(id: id)\n\n        expect { repository.find(id) }\n          .to raise_error(Hanami::Model::MissingPrimaryKeyError, \"Missing primary key for :labels\")\n      end\n    end\n\n    # See https://github.com/hanami/model/issues/399\n    describe \"with custom relation\" do\n      it \"finds record by primary key\" do\n        repository = AccessTokenRepository.new\n        access_token = repository.create(token: \"123\")\n        found        = repository.find(access_token.id)\n\n        expect(found).to eq(access_token)\n      end\n    end\n  end\n\n  describe \"#all\" do\n    it \"returns all the records\" do\n      repository = UserRepository.new\n      user = repository.create(name: \"L\")\n\n      expect(repository.all).to be_an_instance_of(Array)\n      expect(repository.all).to include(user)\n    end\n  end\n\n  describe \"#first\" do\n    it \"returns first record from table\" do\n      repository = UserRepository.new\n      repository.clear\n\n      user = repository.create(name: \"James Hetfield\")\n      repository.create(name: \"Tom\")\n\n      expect(repository.first).to eq(user)\n    end\n  end\n\n  describe \"#last\" do\n    it \"returns last record from table\" do\n      repository = UserRepository.new\n      repository.clear\n\n      repository.create(name: \"Tom\")\n      user = repository.create(name: \"Ella Fitzgerald\")\n\n      expect(repository.last).to eq(user)\n    end\n  end\n\n  # https://github.com/hanami/model/issues/473\n  describe \"querying\" do\n    it \"allows to access relation attributes via square bracket syntax\" do\n      repository = UserRepository.new\n      repository.clear\n\n      expected = [repository.create(name: \"Ella\"),\n                  repository.create(name: \"Bella\")]\n      repository.create(name: \"Jon\")\n\n      actual = repository.by_matching_name(\"%ella%\")\n      expect(actual).to eq(expected)\n    end\n  end\n\n  describe \"#clear\" do\n    it \"clears all the records\" do\n      repository = UserRepository.new\n      repository.create(name: \"L\")\n\n      repository.clear\n      expect(repository.all).to be_empty\n    end\n  end\n\n  describe \"relation\" do\n    describe \"read\" do\n      it \"reads records from the database given a raw query string\" do\n        repository = UserRepository.new\n        repository.create(name: \"L\")\n\n        users = repository.find_all_by_manual_query\n        expect(users).to be_a_kind_of(Array)\n\n        user = users.first\n        expect(user).to be_a_kind_of(User)\n      end\n    end\n  end\n\n  describe \"#create\" do\n    it \"creates record from data\" do\n      repository = UserRepository.new\n      user = repository.create(name: \"L\")\n\n      expect(user).to be_an_instance_of(User)\n      expect(user.id).to_not be_nil\n      expect(user.name).to eq(\"L\")\n    end\n\n    it \"creates record from entity\" do\n      entity = User.new(name: \"L\")\n      repository = UserRepository.new\n      user = repository.create(entity)\n\n      # It doesn't mutate original entity\n      expect(entity.id).to be_nil\n\n      expect(user).to be_an_instance_of(User)\n      expect(user.id).to_not be_nil\n      expect(user.name).to eq(\"L\")\n    end\n\n    with_platform(engine: :jruby, db: :sqlite) do\n      it \"automatically touches timestamps\"\n    end\n\n    unless_platform(engine: :jruby, db: :sqlite) do\n      it \"automatically touches timestamps\" do\n        repository = UserRepository.new\n        user = repository.create(name: \"L\")\n\n        expect(user.created_at).to be_within(2).of(Time.now.utc)\n        expect(user.updated_at).to be_within(2).of(Time.now.utc)\n      end\n\n      it \"respects given timestamps\" do\n        repository = UserRepository.new\n        given_time = Time.new(2010, 1, 1, 12, 0, 0, \"+00:00\")\n\n        user = repository.create(name: \"L\", created_at: given_time, updated_at: given_time)\n\n        expect(user.created_at).to be_within(2).of(given_time)\n        expect(user.updated_at).to be_within(2).of(given_time)\n      end\n\n      it \"can update timestamps\" do\n        repository = UserRepository.new\n        user = repository.create(name: \"L\")\n        expect(user.created_at).to be_within(2).of(Time.now.utc)\n        expect(user.updated_at).to be_within(2).of(Time.now.utc)\n\n        given_time = Time.new(2010, 1, 1, 12, 0, 0, \"+00:00\")\n        updated = repository.update(\n          user.id,\n          created_at: given_time,\n          updated_at: given_time\n        )\n\n        expect(updated.name).to eq(\"L\")\n        expect(updated.created_at).to be_within(2).of(given_time)\n        expect(updated.updated_at).to be_within(2).of(given_time)\n      end\n\n      # Bug: https://github.com/hanami/model/issues/412\n      it \"can have only creation timestamp\" do\n        user = UserRepository.new.create(name: \"L\")\n        repository = AvatarRepository.new\n        account = repository.create(url: \"http://foo.com\", user_id: user.id)\n        expect(account.created_at).to be_within(2).of(Time.now.utc)\n      end\n    end\n\n    # Bug: https://github.com/hanami/model/issues/237\n    it \"respects database defaults\" do\n      repository = UserRepository.new\n      user = repository.create(name: \"L\")\n\n      expect(user.comments_count).to eq(0)\n    end\n\n    # Bug: https://github.com/hanami/model/issues/272\n    it \"accepts booleans as attributes\" do\n      user = UserRepository.new.create(name: \"L\", active: false)\n      expect(user.active).to eq(false)\n    end\n\n    it \"raises error when generic database error is raised\"\n    # it 'raises error when generic database error is raised' do\n    #   expected_error = Hanami::Model::DatabaseError\n    #   message = Platform.match do\n    #     engine(:ruby).db(:sqlite)  { 'SQLite3::SQLException: table users has no column named bogus' }\n    #     engine(:jruby).db(:sqlite) { 'Java::JavaSql::SQLException: table users has no column named bogus' }\n\n    #     engine(:ruby).db(:postgresql)  { 'PG::UndefinedColumn: ERROR:  column \"bogus\" of relation \"users\" does not exist' }\n    #     engine(:jruby).db(:postgresql) { 'bogus' }\n\n    #     engine(:ruby).db(:mysql)  { \"Mysql2::Error: Unknown column 'bogus' in 'field list'\" }\n    #     engine(:jruby).db(:mysql) { 'bogus' }\n    #   end\n\n    #   expect { UserRepository.new.create(name: 'L', bogus: 23) }.to raise_error do |error|\n    #     expect(error).to be_a(expected_error)\n    #     expect(error.message).to include(message)\n    #   end\n    # end\n\n    it 'raises error when \"not null\" database constraint is violated' do\n      expected_error = Hanami::Model::NotNullConstraintViolationError\n      message = Platform.match do\n        engine(:ruby).db(:sqlite)  { \"SQLite3::ConstraintException\" }\n        engine(:jruby).db(:sqlite) { \"Java::OrgSqlite::SQLiteException: [SQLITE_CONSTRAINT_NOTNULL]  A NOT NULL constraint failed (NOT NULL constraint failed: users.active)\" }\n\n        engine(:ruby).db(:postgresql)  { 'PG::NotNullViolation: ERROR:  null value in column \"active\" of relation \"users\" violates not-null constraint' }\n        engine(:jruby).db(:postgresql) { 'Java::OrgPostgresqlUtil::PSQLException: ERROR: null value in column \"active\" violates not-null constraint' }\n\n        engine(:ruby).db(:mysql)  { \"Mysql2::Error: Column 'active' cannot be null\" }\n        engine(:jruby).db(:mysql) { \"Java::ComMysqlJdbcExceptionsJdbc4::MySQLIntegrityConstraintViolationException: Column 'active' cannot be null\" }\n      end\n\n      expect { UserRepository.new.create(name: \"L\", active: nil) }.to raise_error do |error|\n        expect(error).to be_a(expected_error)\n        expect(error.message).to include(message)\n      end\n    end\n\n    it 'raises error when \"unique constraint\" is violated' do\n      email = \"user@#{SecureRandom.uuid}.test\"\n\n      expected_error = Hanami::Model::UniqueConstraintViolationError\n      message = Platform.match do\n        engine(:ruby).db(:sqlite)  { \"SQLite3::ConstraintException\" }\n        engine(:jruby).db(:sqlite) { \"Java::OrgSqlite::SQLiteException: [SQLITE_CONSTRAINT_UNIQUE]  A UNIQUE constraint failed (UNIQUE constraint failed: users.email)\" }\n\n        engine(:ruby).db(:postgresql)  { 'PG::UniqueViolation: ERROR:  duplicate key value violates unique constraint \"users_email_index\"' }\n        engine(:jruby).db(:postgresql) { %(Java::OrgPostgresqlUtil::PSQLException: ERROR: duplicate key value violates unique constraint \"users_email_index\"\\n  Detail: Key (email)=(#{email}) already exists.) }\n\n        engine(:ruby).db(:mysql)  { \"Mysql2::Error: Duplicate entry '#{email}' for key 'users.users_email_index'\" }\n        engine(:jruby).db(:mysql) { \"Java::ComMysqlJdbcExceptionsJdbc4::MySQLIntegrityConstraintViolationException: Duplicate entry '#{email}' for key 'users_email_index'\" }\n      end\n\n      repository = UserRepository.new\n      repository.create(name: \"Test\", email: email)\n\n      expect { repository.create(name: \"L\", email: email) }.to raise_error do |error|\n        expect(error).to be_a(expected_error)\n        expect(error.message).to include(message)\n      end\n    end\n\n    it 'raises error when \"foreign key\" constraint is violated' do\n      expected_error = Hanami::Model::ForeignKeyConstraintViolationError\n      message = Platform.match do\n        engine(:ruby).db(:sqlite)  { \"SQLite3::ConstraintException\" }\n        engine(:jruby).db(:sqlite) { \"Java::OrgSqlite::SQLiteException: [SQLITE_CONSTRAINT_FOREIGNKEY]  A foreign key constraint failed (FOREIGN KEY constraint failed)\" }\n\n        engine(:ruby).db(:postgresql)  { 'PG::ForeignKeyViolation: ERROR:  insert or update on table \"avatars\" violates foreign key constraint \"avatars_user_id_fkey\"' }\n        engine(:jruby).db(:postgresql) { 'Java::OrgPostgresqlUtil::PSQLException: ERROR: insert or update on table \"avatars\" violates foreign key constraint \"avatars_user_id_fkey\"' }\n\n        engine(:ruby).db(:mysql)  { \"Mysql2::Error: Cannot add or update a child row: a foreign key constraint fails\" }\n        engine(:jruby).db(:mysql) { \"Java::ComMysqlJdbcExceptionsJdbc4::MySQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`hanami_model`.`avatars`, CONSTRAINT `avatars_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE)\" }\n      end\n\n      expect { AvatarRepository.new.create(user_id: 999_999_999, url: \"url\") }.to raise_error do |error|\n        expect(error).to be_a(expected_error)\n        expect(error.message).to include(message)\n      end\n    end\n\n    # For MySQL [...] The CHECK clause is parsed but ignored by all storage engines.\n    # http://dev.mysql.com/doc/refman/5.7/en/create-table.html\n    unless_platform(db: :mysql) do\n      it 'raises error when \"check\" constraint is violated' do\n        expected = Hanami::Model::CheckConstraintViolationError\n\n        message = Platform.match do\n          engine(:ruby).db(:sqlite)  { \"SQLite3::ConstraintException: CHECK constraint failed\" }\n          engine(:jruby).db(:sqlite) { \"Java::OrgSqlite::SQLiteException: [SQLITE_CONSTRAINT_CHECK]  A CHECK constraint failed (CHECK constraint failed: users)\" }\n\n          engine(:ruby).db(:postgresql)  { 'PG::CheckViolation: ERROR:  new row for relation \"users\" violates check constraint \"users_age_check\"' }\n          engine(:jruby).db(:postgresql) { 'Java::OrgPostgresqlUtil::PSQLException: ERROR: new row for relation \"users\" violates check constraint \"users_age_check\"' }\n        end\n\n        expect { UserRepository.new.create(name: \"L\", age: 1) }.to raise_error do |error|\n          expect(error).to         be_a(expected)\n          expect(error.message).to include(message)\n        end\n      end\n\n      it \"raises error when constraint is violated\" do\n        expected = Hanami::Model::CheckConstraintViolationError\n\n        message = Platform.match do\n          engine(:ruby).db(:sqlite)  { \"SQLite3::ConstraintException: CHECK constraint failed\" }\n          engine(:jruby).db(:sqlite) { \"Java::OrgSqlite::SQLiteException: [SQLITE_CONSTRAINT_CHECK]  A CHECK constraint failed (CHECK constraint failed: comments_count_constraint)\" }\n\n          engine(:ruby).db(:postgresql)  { 'PG::CheckViolation: ERROR:  new row for relation \"users\" violates check constraint \"comments_count_constraint\"' }\n          engine(:jruby).db(:postgresql) { 'Java::OrgPostgresqlUtil::PSQLException: ERROR: new row for relation \"users\" violates check constraint \"comments_count_constraint\"' }\n        end\n\n        expect { UserRepository.new.create(name: \"L\", comments_count: -1) }.to raise_error do |error|\n          expect(error).to         be_a(expected)\n          expect(error.message).to include(message)\n        end\n      end\n    end\n  end\n\n  describe \"#update\" do\n    it \"updates record from data\" do\n      repository = UserRepository.new\n      user = repository.create(name: \"L\")\n      updated = repository.update(user.id, name: \"Luca\")\n\n      expect(updated).to be_an_instance_of(User)\n      expect(updated.id).to eq(user.id)\n      expect(updated.name).to eq(\"Luca\")\n    end\n\n    it \"updates record from entity\" do\n      entity = User.new(name: \"Luca\")\n      repository = UserRepository.new\n      user = repository.create(name: \"L\")\n      updated = repository.update(user.id, entity)\n\n      # It doesn't mutate original entity\n      expect(entity.id).to be_nil\n\n      expect(updated).to be_an_instance_of(User)\n      expect(updated.id).to eq(user.id)\n      expect(updated.name).to eq(\"Luca\")\n    end\n\n    it \"returns nil when record cannot be found\" do\n      repository = UserRepository.new\n      updated = repository.update(\"9999999\", name: \"Luca\")\n\n      expect(updated).to be_nil\n    end\n\n    with_platform(engine: :jruby, db: :sqlite) do\n      it \"automatically touches timestamps\"\n    end\n\n    unless_platform(engine: :jruby, db: :sqlite) do\n      it \"automatically touches timestamps\" do\n        repository = UserRepository.new\n        user = repository.create(name: \"L\")\n        sleep 0.1\n        updated = repository.update(user.id, name: \"Luca\")\n\n        expect(updated.created_at).to be_within(2).of(user.created_at)\n        expect(updated.updated_at).to be_within(2).of(Time.now)\n      end\n    end\n\n    it \"raises error when generic database error is raised\"\n    # it 'raises error when generic database error is raised' do\n    #   expected_error = Hanami::Model::DatabaseError\n    #   message = Platform.match do\n    #     engine(:ruby).db(:sqlite)  { 'SQLite3::SQLException: no such column: bogus' }\n    #     engine(:jruby).db(:sqlite) { 'Java::JavaSql::SQLException: no such column: bogus' }\n\n    #     engine(:ruby).db(:postgresql)  { 'PG::UndefinedColumn: ERROR:  column \"bogus\" of relation \"users\" does not exist' }\n    #     engine(:jruby).db(:postgresql) { 'bogus' }\n\n    #     engine(:ruby).db(:mysql)  { \"Mysql2::Error: Unknown column 'bogus' in 'field list'\" }\n    #     engine(:jruby).db(:mysql) { 'bogus' }\n    #   end\n\n    #   repository = UserRepository.new\n    #   user = repository.create(name: 'L')\n\n    #   expect { repository.update(user.id, bogus: 23) }.to raise_error do |error|\n    #     expect(error).to be_a(expected_error)\n    #     expect(error.message).to include(message)\n    #   end\n    # end\n\n    # MySQL doesn't raise an error on CI\n    unless_platform(os: :linux, engine: :ruby, db: :mysql) do\n      it 'raises error when \"not null\" database constraint is violated' do\n        expected_error = Hanami::Model::NotNullConstraintViolationError\n        message = Platform.match do\n          engine(:ruby).db(:sqlite)  { \"SQLite3::ConstraintException\" }\n          engine(:jruby).db(:sqlite) { \"Java::OrgSqlite::SQLiteException: [SQLITE_CONSTRAINT_NOTNULL]  A NOT NULL constraint failed (NOT NULL constraint failed: users.active)\" }\n\n          engine(:ruby).db(:postgresql)  { 'PG::NotNullViolation: ERROR:  null value in column \"active\" of relation \"users\" violates not-null constraint' }\n          engine(:jruby).db(:postgresql) { 'Java::OrgPostgresqlUtil::PSQLException: ERROR: null value in column \"active\" violates not-null constraint' }\n\n          engine(:ruby).db(:mysql)  { \"Mysql2::Error: Column 'active' cannot be null\" }\n          engine(:jruby).db(:mysql) { \"Java::ComMysqlJdbcExceptionsJdbc4::MySQLIntegrityConstraintViolationException: Column 'active' cannot be null\" }\n        end\n\n        repository = UserRepository.new\n        user = repository.create(name: \"L\")\n\n        expect { repository.update(user.id, active: nil) }.to raise_error do |error|\n          expect(error).to be_a(expected_error)\n          expect(error.message).to include(message)\n        end\n      end\n    end\n\n    it 'raises error when \"unique constraint\" is violated' do\n      email = \"update@#{SecureRandom.uuid}.test\"\n\n      expected_error = Hanami::Model::UniqueConstraintViolationError\n      message = Platform.match do\n        engine(:ruby).db(:sqlite)  { \"SQLite3::ConstraintException\" }\n        engine(:jruby).db(:sqlite) { \"Java::OrgSqlite::SQLiteException: [SQLITE_CONSTRAINT_UNIQUE]  A UNIQUE constraint failed (UNIQUE constraint failed: users.email)\" }\n\n        engine(:ruby).db(:postgresql)  { 'PG::UniqueViolation: ERROR:  duplicate key value violates unique constraint \"users_email_index\"' }\n        engine(:jruby).db(:postgresql) { 'Java::OrgPostgresqlUtil::PSQLException: ERROR: duplicate key value violates unique constraint \"users_email_index\"' }\n\n        engine(:ruby).db(:mysql)  { \"Mysql2::Error: Duplicate entry '#{email}' for key 'users.users_email_index'\" }\n        engine(:jruby).db(:mysql) { \"Java::ComMysqlJdbcExceptionsJdbc4::MySQLIntegrityConstraintViolationException: Duplicate entry '#{email}' for key 'users_email_index'\" }\n      end\n\n      repository = UserRepository.new\n      user = repository.create(name: \"L\")\n      repository.create(name: \"UpdateTest\", email: email)\n\n      expect { repository.update(user.id, email: email) }.to raise_error do |error|\n        expect(error).to be_a(expected_error)\n        expect(error.message).to include(message)\n      end\n    end\n\n    it 'raises error when \"foreign key\" constraint is violated' do\n      expected_error = Hanami::Model::ForeignKeyConstraintViolationError\n      message = Platform.match do\n        engine(:ruby).db(:sqlite)  { \"SQLite3::ConstraintException\" }\n        engine(:jruby).db(:sqlite) { \"Java::OrgSqlite::SQLiteException: [SQLITE_CONSTRAINT_FOREIGNKEY]  A foreign key constraint failed (FOREIGN KEY constraint failed)\" }\n\n        engine(:ruby).db(:postgresql)  { 'PG::ForeignKeyViolation: ERROR:  insert or update on table \"avatars\" violates foreign key constraint \"avatars_user_id_fkey\"' }\n        engine(:jruby).db(:postgresql) { 'Java::OrgPostgresqlUtil::PSQLException: ERROR: insert or update on table \"avatars\" violates foreign key constraint \"avatars_user_id_fkey\"' }\n\n        engine(:ruby).db(:mysql)  { \"Mysql2::Error: Cannot add or update a child row: a foreign key constraint fails\" }\n        engine(:jruby).db(:mysql) { \"Java::ComMysqlJdbcExceptionsJdbc4::MySQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`hanami_model`.`avatars`, CONSTRAINT `avatars_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE)\" }\n      end\n\n      user = UserRepository.new.create(name: \"L\")\n      repository = AvatarRepository.new\n      avatar = repository.create(user_id: user.id, url: \"a valid url\")\n\n      expect { repository.update(avatar.id, user_id: 999_999_999) }.to raise_error do |error|\n        expect(error).to be_a(expected_error)\n        expect(error.message).to include(message)\n      end\n    end\n\n    # For MySQL [...] The CHECK clause is parsed but ignored by all storage engines.\n    # http://dev.mysql.com/doc/refman/5.7/en/create-table.html\n    unless_platform(db: :mysql) do\n      it 'raises error when \"check\" constraint is violated' do\n        expected = Hanami::Model::CheckConstraintViolationError\n\n        message = Platform.match do\n          engine(:ruby).db(:sqlite)   { \"SQLite3::ConstraintException: CHECK constraint failed\" }\n          engine(:jruby).db(:sqlite)  { \"Java::OrgSqlite::SQLiteException: [SQLITE_CONSTRAINT_CHECK]  A CHECK constraint failed (CHECK constraint failed: users)\" }\n\n          engine(:ruby).db(:postgresql)  { 'PG::CheckViolation: ERROR:  new row for relation \"users\" violates check constraint \"users_age_check\"' }\n          engine(:jruby).db(:postgresql) { 'Java::OrgPostgresqlUtil::PSQLException: ERROR: new row for relation \"users\" violates check constraint \"users_age_check\"' }\n        end\n\n        repository = UserRepository.new\n        user = repository.create(name: \"L\")\n\n        expect { repository.update(user.id, age: 17) }.to raise_error do |error|\n          expect(error).to         be_a(expected)\n          expect(error.message).to include(message)\n        end\n      end\n\n      it \"raises error when constraint is violated\" do\n        expected = Hanami::Model::CheckConstraintViolationError\n\n        message = Platform.match do\n          engine(:ruby).db(:sqlite)   { \"SQLite3::ConstraintException: CHECK constraint failed\" }\n          engine(:jruby).db(:sqlite)  { \"Java::OrgSqlite::SQLiteException: [SQLITE_CONSTRAINT_CHECK]  A CHECK constraint failed (CHECK constraint failed: comments_count_constraint)\" }\n\n          engine(:ruby).db(:postgresql)  { 'PG::CheckViolation: ERROR:  new row for relation \"users\" violates check constraint \"comments_count_constraint\"' }\n          engine(:jruby).db(:postgresql) { 'Java::OrgPostgresqlUtil::PSQLException: ERROR: new row for relation \"users\" violates check constraint \"comments_count_constraint\"' }\n        end\n\n        repository = UserRepository.new\n        user = repository.create(name: \"L\")\n\n        expect { repository.update(user.id, comments_count: -2) }.to raise_error do |error|\n          expect(error).to         be_a(expected)\n          expect(error.message).to include(message)\n        end\n      end\n    end\n  end\n\n  describe \"#delete\" do\n    it \"deletes record\" do\n      repository = UserRepository.new\n      user = repository.create(name: \"L\")\n      deleted = repository.delete(user.id)\n\n      expect(deleted).to be_an_instance_of(User)\n      expect(deleted.id).to eq(user.id)\n      expect(deleted.name).to eq(\"L\")\n\n      found = repository.find(user.id)\n      expect(found).to be_nil\n    end\n\n    it \"returns nil when record cannot be found\" do\n      repository = UserRepository.new\n      deleted = repository.delete(\"9999999\")\n\n      expect(deleted).to be_nil\n    end\n  end\n\n  describe \"#transaction\" do\n  end\n\n  describe \"custom finder\" do\n    it \"returns records\" do\n      repository = UserRepository.new\n      user = repository.create(name: \"L\")\n      found = repository.by_name(\"L\")\n\n      expect(found.to_a).to include(user)\n    end\n\n    it \"uses root relation\" do\n      repository = UserRepository.new\n      user = repository.create(name: \"L\")\n      found = repository.by_name_with_root(\"L\")\n\n      expect(found.to_a).to include(user)\n    end\n\n    it \"selects only a single column\" do\n      repository = UserRepository.new\n      repository.clear\n\n      repository.create([{name: \"L\", age: 35}, {name: \"MG\", age: 34}])\n      found = repository.ids\n\n      expect(found.size).to be(2)\n      found.each do |user|\n        expect(user).to be_a_kind_of(User)\n        expect(user.id).to_not be(nil)\n        expect(user.name).to be(nil)\n        expect(user.age).to be(nil)\n      end\n    end\n\n    it \"selects multiple columns\" do\n      repository = UserRepository.new\n      repository.clear\n\n      repository.create([{name: \"L\", age: 35}, {name: \"MG\", age: 34}])\n      found = repository.select_id_and_name\n\n      expect(found.size).to be(2)\n      found.each do |user|\n        expect(user).to be_a_kind_of(User)\n        expect(user.id).to_not be(nil)\n        expect(user.name).to_not be(nil)\n        expect(user.age).to be(nil)\n      end\n    end\n  end\n\n  with_platform(db: :postgresql) do\n    describe \"PostgreSQL\" do\n      it \"finds record by primary key (UUID)\" do\n        repository = SourceFileRepository.new\n        file = repository.create(name: \"path/to/file.rb\", languages: [\"ruby\"], metadata: {coverage: 100.0}, content: \"class Foo; end\")\n        found = repository.find(file.id)\n\n        expect(file.languages).to eq([\"ruby\"])\n        expect(file.metadata).to eq(coverage: 100.0)\n\n        expect(found).to eq(file)\n      end\n\n      it \"returns nil for nil primary key (UUID)\" do\n        repository = SourceFileRepository.new\n\n        found = repository.find(nil)\n        expect(found).to be_nil\n      end\n\n      # FIXME: This raises the following error\n      #\n      #   Sequel::DatabaseError: PG::InvalidTextRepresentation: ERROR:  invalid input syntax for uuid: \"9999999\"\n      #   LINE 1: ...\", \"updated_at\" FROM \"source_files\" WHERE (\"id\" = '9999999')...\n      it \"returns nil for missing record (UUID)\"\n      # it 'returns nil for missing record (UUID)' do\n      #   repository = SourceFileRepository.new\n\n      #   found = repository.find('9999999')\n      #   expect(found).to be_nil\n      # end\n\n      describe \"JSON types\" do\n        it \"writes hashes\" do\n          hash = {first_name: \"John\", age: 53, married: true, car: nil}\n          repository = SourceFileRepository.new\n          column_type = repository.create(metadata: hash, name: \"test\", content: \"test\", json_info: hash)\n          found = repository.find(column_type.id)\n\n          expect(found.metadata).to eq(hash)\n          expect(found.json_info).to eq(hash)\n        end\n\n        it \"writes arrays\" do\n          array = [\"abc\", 1, true, nil]\n          repository = SourceFileRepository.new\n          column_type = repository.create(metadata: array, name: \"test\", content: \"test\", json_info: array)\n          found = repository.find(column_type.id)\n\n          expect(found.metadata).to eq(array)\n          expect(found.json_info).to eq(array)\n        end\n      end\n\n      describe \"when timestamps aren't enabled\" do\n        it \"writes the proper PG types\" do\n          repository = ProductRepository.new\n\n          product = repository.create(name: \"NeoVim\", categories: [\"software\"])\n          found = repository.find(product.id)\n\n          expect(product.categories).to eq([\"software\"])\n\n          expect(found).to eq(product)\n        end\n\n        it \"succeeds even if timestamps is the only plugin\" do\n          repository = ProductRepository.new\n\n          product = repository\n            .command(:create, repository.root, use: %i[timestamps])\n            .call(name: \"NeoVim\", categories: [\"software\"])\n\n          found = repository.find(product.id)\n\n          expect(product.categories).to eq([\"software\"])\n\n          expect(found.to_h).to eq(product.to_h)\n        end\n      end\n    end\n\n    describe \"enum database type\" do\n      it \"allows to write data\" do\n        repository = ColorRepository.new\n        color = repository.create(name: \"red\")\n\n        expect(color).to be_a_kind_of(Color)\n        expect(color.name).to eq(\"red\")\n      end\n\n      it \"raises error if the value is not included in the enum\" do\n        repository = ColorRepository.new\n        message = Platform.match do\n          engine(:ruby)  { %(PG::InvalidTextRepresentation: ERROR:  invalid input value for enum rainbow: \"grey\") }\n          engine(:jruby) { %(Java::OrgPostgresqlUtil::PSQLException: ERROR: invalid input value for enum rainbow: \"grey\") }\n        end\n\n        expect { repository.create(name: \"grey\") }.to raise_error do |error|\n          expect(error).to be_a(Hanami::Model::Error)\n          expect(error.message).to include(message)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/integration/hanami/model/repository/command_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe \"Customized commands\" do\n  subject(:authors) { AuthorRepository.new }\n\n  let(:data) do\n    [{name: \"Arthur C. Clarke\"}, {name: \"Phillip K. Dick\"}]\n  end\n\n  context \"the mapper\" do\n    it \"is enabled by default\" do\n      result = authors.create_many(data)\n      expect(result).to be_an Array\n      expect(result).to all(be_an(Author))\n    end\n\n    it \"can be explictly turned off\" do\n      result = authors.create_many(data, opts: {mapper: nil})\n      expect(result).to all(be_an(ROM::Struct))\n    end\n  end\n\n  context \"timestamps\" do\n    it \"are enabled by default\" do\n      result = authors.create_many(data)\n      expect(result.first.created_at).to be_within(2).of(Time.now.utc)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/integration/hanami/model/repository/legacy_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe \"Repository (legacy)\" do\n  describe \"#find\" do\n    it \"finds record by primary key\" do\n      repository = OperatorRepository.new\n      operator = repository.create(name: \"F\")\n      found = repository.find(operator.id)\n\n      expect(operator).to eq(found)\n    end\n\n    it \"returns nil for missing record\" do\n      repository = OperatorRepository.new\n      found = repository.find(\"9999999\")\n\n      expect(found).to be_nil\n    end\n  end\n\n  describe \"#all\" do\n    it \"returns all the records\" do\n      repository = OperatorRepository.new\n      operator = repository.create(name: \"F\")\n\n      expect(repository.all).to be_an_instance_of(Array)\n      expect(repository.all).to include(operator)\n    end\n  end\n\n  describe \"#first\" do\n    it \"returns first record from table\" do\n      repository = OperatorRepository.new\n      repository.clear\n\n      operator = repository.create(name: \"Janis Joplin\")\n      repository.create(name: \"Jon\")\n\n      expect(repository.first).to eq(operator)\n    end\n  end\n\n  describe \"#last\" do\n    it \"returns last record from table\" do\n      repository = OperatorRepository.new\n      repository.clear\n\n      repository.create(name: \"Rob\")\n      operator = repository.create(name: \"Amy Winehouse\")\n\n      expect(repository.last).to eq(operator)\n    end\n  end\n\n  describe \"#clear\" do\n    it \"clears all the records\" do\n      repository = OperatorRepository.new\n      repository.create(name: \"F\")\n\n      repository.clear\n      expect(repository.all).to be_empty\n    end\n  end\n\n  describe \"#execute\" do\n  end\n\n  describe \"#fetch\" do\n  end\n\n  describe \"#create\" do\n    it \"creates record\" do\n      repository = OperatorRepository.new\n      operator = repository.create(name: \"F\")\n\n      expect(operator).to be_an_instance_of(Operator)\n      expect(operator.id).to_not be_nil\n      expect(operator.name).to eq(\"F\")\n    end\n  end\n\n  describe \"#update\" do\n    it \"updates record\" do\n      repository = OperatorRepository.new\n      operator = repository.create(name: \"F\")\n      updated = repository.update(operator.id, name: \"Flo\")\n\n      expect(updated).to be_an_instance_of(Operator)\n      expect(updated.id).to eq(operator.id)\n      expect(updated.name).to eq(\"Flo\")\n    end\n\n    it \"returns nil when record cannot be found\" do\n      repository = OperatorRepository.new\n      updated = repository.update(\"9999999\", name: \"Flo\")\n\n      expect(updated).to be_nil\n    end\n  end\n\n  describe \"#delete\" do\n    it \"deletes record\" do\n      repository = OperatorRepository.new\n      operator = repository.create(name: \"F\")\n      deleted = repository.delete(operator.id)\n\n      expect(deleted).to be_an_instance_of(Operator)\n      expect(deleted.id).to eq(operator.id)\n      expect(deleted.name).to eq(\"F\")\n\n      found = repository.find(operator.id)\n      expect(found).to be_nil\n    end\n\n    it \"returns nil when record cannot be found\" do\n      repository = OperatorRepository.new\n      deleted = repository.delete(\"9999999\")\n\n      expect(deleted).to be_nil\n    end\n  end\n\n  describe \"#transaction\" do\n  end\nend\n"
  },
  {
    "path": "spec/spec_helper.rb",
    "content": "# frozen_string_literal: true\n\n$LOAD_PATH.unshift \"lib\"\nrequire \"hanami/devtools/unit\"\nrequire \"hanami/model\"\n\nrequire_relative \"./support/rspec\"\nrequire_relative \"./support/test_io\"\nrequire_relative \"./support/platform\"\nrequire_relative \"./support/database\"\nrequire_relative \"./support/fixtures\"\n"
  },
  {
    "path": "spec/support/database/strategies/abstract.rb",
    "content": "# frozen_string_literal: true\n\nmodule Database\n  module Strategies\n    class Abstract\n      def self.eligible?(_adapter)\n        false\n      end\n\n      def run\n        before\n        load_dependencies\n        export_env\n        create_database\n        configure\n        after\n        sleep 1\n      end\n\n      protected\n\n      def before\n        # Optional hook for subclasses\n      end\n\n      def database_name\n        \"hanami_model\"\n      end\n\n      def load_dependencies\n        raise NoMethodError\n      end\n\n      def export_env\n        ENV[\"HANAMI_DATABASE_NAME\"] = database_name\n      end\n\n      def create_database\n        raise NoMethodError\n      end\n\n      def configure\n        returing = Hanami::Model.configure do\n          adapter ENV[\"HANAMI_DATABASE_ADAPTER\"].to_sym, ENV[\"HANAMI_DATABASE_URL\"]\n        end\n\n        returing == Hanami::Model or raise \"Hanami::Model.configure should return Hanami::Model\"\n      end\n\n      def after\n        # Optional hook for subclasses\n      end\n\n      private\n\n      def jruby?\n        Platform::Engine.engine?(:jruby)\n      end\n\n      def ci?\n        Platform.ci?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/database/strategies/mysql.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative \"sql\"\n\nmodule Database\n  module Strategies\n    class Mysql < Sql\n      module JrubyImplementation\n        protected\n\n        def load_dependencies\n          require \"hanami/model/sql\"\n          require \"jdbc/mysql\"\n        end\n\n        def export_env\n          super\n          ENV[\"HANAMI_DATABASE_URL\"] = \"jdbc:mysql://#{host}/#{database_name}?#{credentials}\"\n        end\n\n        def host\n          ENV[\"HANAMI_DATABASE_HOST\"] || \"127.0.0.1\"\n        end\n\n        def credentials\n          Hash[\n            \"user\" => ENV[\"HANAMI_DATABASE_USERNAME\"],\n            \"password\" => ENV[\"HANAMI_DATABASE_PASSWORD\"],\n            \"useSSL\" => \"false\"\n          ].map do |key, value|\n            \"#{key}=#{value}\" unless Hanami::Utils::Blank.blank?(value)\n          end.compact.join(\"&\")\n        end\n      end\n\n      module TravisCiImplementation\n        protected\n\n        def export_env\n          super\n          ENV[\"HANAMI_DATABASE_USERNAME\"] = \"travis\"\n          ENV[\"HANAMI_DATABASE_URL\"] = \"mysql2://#{credentials}@#{host}/#{database_name}\"\n        end\n\n        def create_database\n          super\n          run_command \"GRANT ALL PRIVILEGES ON *.* TO '#{ENV['HANAMI_DATABASE_USERNAME']}'@'#{host}'; FLUSH PRIVILEGES;\"\n          run_command \"GRANT ALL PRIVILEGES ON *.* TO '#{ENV['HANAMI_DATABASE_USERNAME']}'@'%'; FLUSH PRIVILEGES;\" if jruby?\n        end\n\n        private\n\n        def run_command(command)\n          result = system %(mysql -u root -e \"#{command}\")\n          raise \"Failed command:\\n#{command}\" unless result\n        end\n      end\n\n      module CircleCiImplementation\n        protected\n\n        def export_env\n          super\n          ENV[\"HANAMI_DATABASE_USERNAME\"] ||= \"root\"\n          ENV[\"HANAMI_DATABASE_URL\"] = \"mysql2://#{credentials}@#{host}/#{database_name}\"\n        end\n\n        def create_database\n          run_command \"DROP DATABASE IF EXISTS #{database_name}\"\n          run_command \"CREATE DATABASE #{database_name}\"\n        end\n\n        private\n\n        def run_command(command)\n          result = system %(mysql -h #{host} -u #{ENV['HANAMI_DATABASE_USERNAME']} --password=#{ENV['HANAMI_DATABASE_PASSWORD']} -e \"#{command}\")\n          raise \"Failed command:\\n#{command}\" unless result\n        end\n      end\n\n      def self.eligible?(adapter)\n        adapter.start_with?(\"mysql\")\n      end\n\n      def initialize\n        ci_implementation = Platform.match do\n          ci(:travis) { TravisCiImplementation }\n          ci(:circle) { CircleCiImplementation }\n          default { Module.new }\n        end\n\n        extend(ci_implementation)\n        extend(JrubyImplementation) if jruby?\n      end\n\n      protected\n\n      def load_dependencies\n        require \"hanami/model/sql\"\n        require \"mysql2\"\n      end\n\n      def export_env\n        super\n        ENV[\"HANAMI_DATABASE_TYPE\"] = \"mysql\"\n        ENV[\"HANAMI_DATABASE_USERNAME\"] ||= \"root\"\n        ENV[\"HANAMI_DATABASE_PASSWORD\"] ||= \"\"\n        ENV[\"HANAMI_DATABASE_URL\"] = \"mysql2://#{credentials}@#{host}/#{database_name}\"\n      end\n\n      def create_database\n        run_command \"DROP DATABASE IF EXISTS #{database_name}\"\n        run_command \"CREATE DATABASE #{database_name}\"\n        run_command \"GRANT ALL PRIVILEGES ON #{database_name}.* TO '#{ENV['HANAMI_DATABASE_USERNAME']}'@'#{host}'; FLUSH PRIVILEGES;\"\n      end\n\n      private\n\n      def run_command(command)\n        system %(mysql -u #{ENV['HANAMI_DATABASE_USERNAME']} -e \"#{command}\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/database/strategies/postgresql.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative \"sql\"\n\nmodule Database\n  module Strategies\n    class Postgresql < Sql\n      module JrubyImplementation\n        protected\n\n        def load_dependencies\n          require \"hanami/model/sql\"\n          require \"jdbc/postgres\"\n\n          Jdbc::Postgres.load_driver\n        end\n\n        def export_env\n          super\n          ENV[\"HANAMI_DATABASE_URL\"] = \"jdbc:postgresql://#{host_and_credentials}/#{database_name}\"\n        end\n      end\n\n      module TravisCiImplementation\n        protected\n\n        def export_env\n          super\n          ENV[\"HANAMI_DATABASE_USERNAME\"] = \"postgres\"\n        end\n      end\n\n      module CircleCiImplementation\n        protected\n\n        def create_database\n          try(\"Failed to drop Postgres database: #{database_name}\") do\n            system \"dropdb --host=#{ENV['HANAMI_DATABASE_HOST']} --username=#{ENV['HANAMI_DATABASE_USERNAME']} --if-exists #{database_name}\"\n          end\n\n          try(\"Failed to create Postgres database: #{database_name}\") do\n            system \"createdb --host=#{ENV['HANAMI_DATABASE_HOST']} --username=#{ENV['HANAMI_DATABASE_USERNAME']} #{database_name}\"\n          end\n        end\n      end\n\n      module GithubActionsImplementation\n        protected\n\n        def export_env\n          super\n          ENV[\"HANAMI_DATABASE_HOST\"] = \"localhost\"\n          ENV[\"HANAMI_DATABASE_URL\"] = \"postgres://#{credentials}@#{host}/#{database_name}\"\n        end\n\n        def create_database\n          try(\"Failed to drop Postgres database: #{database_name}\") do\n            system \"PGPASSWORD=#{ENV['HANAMI_DATABASE_PASSWORD']} dropdb --host=#{ENV['HANAMI_DATABASE_HOST']} --username=#{ENV['HANAMI_DATABASE_USERNAME']} --if-exists #{database_name}\"\n          end\n\n          try(\"Failed to create Postgres database: #{database_name}\") do\n            system \"PGPASSWORD=#{ENV['HANAMI_DATABASE_PASSWORD']} createdb --host=#{ENV['HANAMI_DATABASE_HOST']} --username=#{ENV['HANAMI_DATABASE_USERNAME']} #{database_name}\"\n          end\n        end\n      end\n\n      def self.eligible?(adapter)\n        adapter.start_with?(\"postgres\")\n      end\n\n      def initialize\n        ci_implementation = Platform.match do\n          ci(:travis) { TravisCiImplementation }\n          ci(:circle) { CircleCiImplementation }\n          ci(:github) { GithubActionsImplementation }\n          default { Module.new }\n        end\n\n        extend(ci_implementation)\n        extend(JrubyImplementation) if jruby?\n      end\n\n      protected\n\n      def load_dependencies\n        require \"hanami/model/sql\"\n        require \"pg\"\n      end\n\n      def create_database\n        env = {\n          \"PGHOST\" => ENV[\"HANAMI_DATABASE_HOST\"],\n          \"PGUSER\" => ENV[\"HANAMI_DATABASE_USERNAME\"],\n          \"PGPASSWORD\" => ENV[\"HANAMI_DATABASE_PASSWORD\"]\n        }\n\n        try(\"Failed to drop Postgres database: #{database_name}\") do\n          system env, \"dropdb --if-exists #{database_name}\"\n        end\n\n        try(\"Failed to create Postgres database: #{database_name}\") do\n          system env, \"createdb #{database_name}\"\n        end\n      end\n\n      def export_env\n        super\n        ENV[\"HANAMI_DATABASE_TYPE\"] = \"postgresql\"\n        ENV[\"HANAMI_DATABASE_USERNAME\"] ||= `whoami`.strip.freeze\n        ENV[\"HANAMI_DATABASE_URL\"] = \"postgres://#{credentials}@#{host}/#{database_name}\"\n      end\n\n      private\n\n      def try(message)\n        yield\n      rescue\n        warn message\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/database/strategies/sql.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative \"abstract\"\nrequire \"hanami/utils/blank\"\nrequire \"pathname\"\nrequire \"stringio\"\n\nmodule Database\n  module Strategies\n    class Sql < Abstract\n      def self.eligible?(_adapter)\n        false\n      end\n\n      protected\n\n      def before\n        super\n        logger.unlink if logger.exist?\n        logger.dirname.mkpath\n      end\n\n      def export_env\n        super\n        ENV[\"HANAMI_DATABASE_ADAPTER\"] = \"sql\"\n        ENV[\"HANAMI_DATABASE_LOGGER\"] = logger.to_s\n      end\n\n      def configure\n        Hanami::Model.configure do\n          adapter    ENV[\"HANAMI_DATABASE_ADAPTER\"].to_sym, ENV[\"HANAMI_DATABASE_URL\"]\n          logger     ENV[\"HANAMI_DATABASE_LOGGER\"], level: :debug\n          migrations Dir.pwd + \"/spec/support/fixtures/database_migrations\"\n          schema     Dir.pwd + \"/tmp/schema.sql\"\n\n          migrations_logger ENV[\"HANAMI_DATABASE_LOGGER\"]\n\n          gateway do |g|\n            g.connection.extension(:pg_enum) if Database.engine?(:postgresql)\n          end\n        end\n      end\n\n      def after\n        migrate\n        puts \"Testing with `#{ENV['HANAMI_DATABASE_ADAPTER']}' adapter (#{ENV['HANAMI_DATABASE_TYPE']}) - jruby: #{jruby?}, ci: #{ci?}\"\n        puts \"Env: #{ENV.inspect}\" if ci?\n      end\n\n      def migrate\n        TestIO.with_stdout do\n          require \"hanami/model/migrator\"\n          Hanami::Model::Migrator.migrate\n        end\n      end\n\n      def credentials\n        [ENV[\"HANAMI_DATABASE_USERNAME\"], ENV[\"HANAMI_DATABASE_PASSWORD\"]].reject do |token|\n          Hanami::Utils::Blank.blank?(token)\n        end.join(\":\")\n      end\n\n      def host\n        ENV[\"HANAMI_DATABASE_HOST\"] || \"localhost\"\n      end\n\n      def host_and_credentials\n        result = [host]\n        result.unshift(credentials) unless Hanami::Utils::Blank.blank?(credentials)\n        result.join(\"@\")\n      end\n\n      def logger\n        Pathname.new(\"tmp\").join(\"hanami_model.log\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/database/strategies/sqlite.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative \"sql\"\nrequire \"pathname\"\n\nmodule Database\n  module Strategies\n    class Sqlite < Sql\n      module JrubyImplementation\n        protected\n\n        def load_dependencies\n          require \"hanami/model/sql\"\n          require \"jdbc/sqlite3\"\n          Jdbc::SQLite3.load_driver\n        end\n\n        def export_env\n          super\n          ENV[\"HANAMI_DATABASE_URL\"] = \"jdbc:sqlite://#{database_name}\"\n        end\n      end\n\n      module CiImplementation\n      end\n\n      def self.eligible?(adapter)\n        adapter.start_with?(\"sqlite\")\n      end\n\n      def initialize\n        extend(CiImplementation)    if ci?\n        extend(JrubyImplementation) if jruby?\n      end\n\n      protected\n\n      def database_name\n        Pathname.new(__dir__).join(\"..\", \"..\", \"..\", \"..\", \"tmp\", \"sqlite\", \"#{super}.sqlite3\").to_s\n      end\n\n      def load_dependencies\n        require \"hanami/model/sql\"\n        require \"sqlite3\"\n      end\n\n      def create_database\n        path = Pathname.new(database_name)\n        path.dirname.mkpath        # create directory if not exist\n\n        path.delete if path.exist? # delete file if exist\n      end\n\n      def export_env\n        super\n        ENV[\"HANAMI_DATABASE_TYPE\"] = \"sqlite\"\n        ENV[\"HANAMI_DATABASE_URL\"] = \"sqlite://#{database_name}\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/database.rb",
    "content": "# frozen_string_literal: true\n\nmodule Database\n  class Setup\n    DEFAULT_ADAPTER = \"sqlite\"\n\n    def initialize(adapter: ENV[\"DB\"])\n      @strategy = Strategy.for(adapter || DEFAULT_ADAPTER)\n    end\n\n    def run\n      @strategy.run\n    end\n  end\n\n  module Strategies\n    require_relative \"./database/strategies/sqlite\"\n    require_relative \"./database/strategies/postgresql\"\n    require_relative \"./database/strategies/mysql\"\n\n    def self.strategies\n      constants.map do |const|\n        const_get(const)\n      end\n    end\n  end\n\n  class Strategy\n    class << self\n      def for(adapter)\n        strategies.find do |strategy|\n          strategy.eligible?(adapter)\n        end.new\n      end\n\n      private\n\n      def strategies\n        Strategies.strategies\n      end\n    end\n  end\n\n  def self.engine\n    ENV[\"HANAMI_DATABASE_TYPE\"].to_sym\n  end\n\n  def self.engine?(name)\n    engine == name.to_sym\n  end\nend\n\nDatabase::Setup.new.run\n"
  },
  {
    "path": "spec/support/fixtures/database_migrations/20150612081248_column_types.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  change do\n    case Database.engine\n    when :sqlite\n      create_table :column_types do\n        column :integer1, Integer\n        column :integer2, :integer\n        column :integer3, \"integer\"\n\n        column :string1, String\n        column :string2, :string\n        column :string3, \"string\"\n        column :string4, \"varchar(3)\"\n\n        column :string5, String, size: 50\n        column :string6, String, fixed: true\n        column :string7, String, fixed: true, size: 64\n        column :string8, String, text: true\n\n        column :file1, File\n        column :file2, \"blob\"\n\n        column :number1, Fixnum # rubocop:disable Lint/UnifiedInteger\n        column :number2, :Bignum\n        column :number3, Float\n        column :number4, BigDecimal\n        column :number5, BigDecimal, size: 10\n        column :number6, BigDecimal, size: [10, 2]\n        column :number7, Numeric\n\n        column :date1, Date\n        column :date2, DateTime\n\n        column :time1, Time\n        column :time2, Time, only_time: true\n\n        column :boolean1, TrueClass\n        column :boolean2, FalseClass\n      end\n    when :postgresql\n      execute 'CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"'\n      execute \"CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');\"\n      execute %{\n        CREATE TYPE inventory_item AS (\n          name            text,\n          supplier_id     integer,\n          price           numeric\n        );\n      }\n\n      create_table :column_types do\n        column :integer1, Integer\n        column :integer2, :integer\n        column :integer3, \"integer\"\n\n        column :string1, String\n        column :string2, \"text\"\n        column :string3, \"character varying(1)\"\n        column :string4, \"varchar(2)\"\n        column :string5, \"character(3)\"\n        column :string6, \"char(4)\"\n\n        column :string7, String, size: 50\n        column :string8, String, fixed: true\n        column :string9, String, fixed: true, size: 64\n        column :string10, String, text: true\n\n        column :file1, File\n        column :file2, \"bytea\"\n\n        column :number1, Fixnum # rubocop:disable Lint/UnifiedInteger\n        column :number2, :Bignum\n        column :number3, Float\n        column :number4, BigDecimal\n        column :number5, BigDecimal, size: 10\n        column :number6, BigDecimal, size: [10, 2]\n        column :number7, Numeric\n\n        column :date1, Date\n        column :date2, DateTime\n\n        column :time1, Time\n        column :time2, Time, only_time: true\n\n        column :boolean1, TrueClass\n        column :boolean2, FalseClass\n\n        column :array1, \"integer[]\"\n        column :array2, \"integer[3]\"\n        column :array3, \"text[][]\"\n\n        column :money1, \"money\"\n\n        column :enum1, \"mood\"\n\n        column :geometric1, \"point\"\n        column :geometric2, \"line\"\n        column :geometric3, \"circle\", default: \"<(15,15), 1>\"\n\n        column :net1, \"cidr\", default: \"192.168/24\"\n\n        column :uuid1, \"uuid\", default: Hanami::Model::Sql.function(:uuid_generate_v4)\n\n        column :xml1, \"xml\"\n\n        column :json1, \"json\"\n        column :json2, \"jsonb\"\n\n        column :composite1, \"inventory_item\", default: Hanami::Model::Sql.literal(\"ROW('fuzzy dice', 42, 1.99)\")\n      end\n    when :mysql\n      create_table :column_types do\n        column :integer1, Integer\n        column :integer2, :integer\n        column :integer3, \"integer\"\n\n        column :string1, String\n        column :string2, \"varchar(3)\"\n\n        column :string5, String, size: 50\n        column :string6, String, fixed: true\n        column :string7, String, fixed: true, size: 64\n        column :string8, String, text: true\n\n        column :file1, File\n        column :file2, \"blob\"\n\n        column :number1, Fixnum # rubocop:disable Lint/UnifiedInteger\n        column :number2, :Bignum\n        column :number3, Float\n        column :number4, BigDecimal\n        column :number5, BigDecimal, size: 10\n        column :number6, BigDecimal, size: [10, 2]\n        column :number7, Numeric\n\n        column :date1, Date\n        column :date2, DateTime\n\n        column :time1, Time\n        column :time2, Time, only_time: true\n\n        column :boolean1, TrueClass\n        column :boolean2, FalseClass\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/database_migrations/20150612084656_default_values.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  change do\n    case Database.engine\n    when :sqlite\n      create_table :default_values do\n        column :a, Integer,    default: 23\n        column :b, String,     default: \"Hanami\"\n        column :c, Fixnum,     default: -1 # rubocop:disable Lint/UnifiedInteger\n        column :d, :Bignum,    default: 0\n        column :e, Float,      default: 3.14\n        column :f, BigDecimal, default: 1.0\n        column :g, Numeric,    default: 943_943\n        column :h, Date,       default: Date.new\n        column :i, DateTime,   default: DateTime.now\n        column :j, Time,       default: Time.now\n        column :k, TrueClass,  default: true\n        column :l, FalseClass, default: false\n      end\n    when :postgresql\n      create_table :default_values do\n        column :a, Integer,    default: 23\n        column :b, String,     default: \"Hanami\"\n        column :c, Fixnum,     default: -1 # rubocop:disable Lint/UnifiedInteger\n        column :d, :Bignum,    default: 0\n        column :e, Float,      default: 3.14\n        column :f, BigDecimal, default: 1.0\n        column :g, Numeric,    default: 943_943\n        column :h, Date,       default: \"now\"\n        column :i, DateTime,   default: DateTime.now\n        column :j, Time,       default: Time.now\n        column :k, TrueClass,  default: true\n        column :l, FalseClass, default: false\n      end\n    when :mysql\n      create_table :default_values do\n        column :a, Integer,    default: 23\n        column :b, String,     default: \"Hanami\"\n        column :c, Fixnum,     default: -1 # rubocop:disable Lint/UnifiedInteger\n        column :d, :Bignum,    default: 0\n        column :e, Float,      default: 3.14\n        column :f, BigDecimal, default: 1.0\n        column :g, Numeric,    default: 943_943\n        column :h, Date # ,       default: 'CURRENT_TIMESTAMP'\n        column :i, DateTime # ,   default: DateTime.now FIXME: see https://github.com/hanami/model/pull/474\n        column :j, Time,       default: Time.now\n        column :k, TrueClass,  default: true\n        column :l, FalseClass, default: false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/database_migrations/20150612093458_null_constraints.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  change do\n    create_table :null_constraints do\n      column :a, Integer\n      column :b, Integer, null: false\n      column :c, Integer, null: true\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/database_migrations/20150612093810_column_indexes.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  change do\n    create_table :column_indexes do\n      column :a, Integer\n      column :b, Integer, index: false\n      column :c, Integer, index: true\n      column :d, Integer\n\n      column :lat, Float\n      column :lng, Float\n\n      index :d, unique: true\n      index %i[b c]\n      index %i[lat lng], name: :column_indexes_coords_index\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/database_migrations/20150612094740_primary_keys.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  change do\n    create_table :primary_keys_1 do\n      primary_key :id\n    end\n\n    create_table :primary_keys_2 do\n      column :name, String, primary_key: true\n    end\n\n    create_table :primary_keys_3 do\n      column :group_id, Integer\n      column :position, Integer\n\n      primary_key %i[group_id position], name: :primary_keys_3_pk\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/database_migrations/20150612115204_foreign_keys.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  change do\n    create_table :artists do\n      primary_key :id\n    end\n\n    create_table :albums do\n      primary_key :id\n      foreign_key :artist_id, :artists, on_delete: :cascade, null: false, type: :integer\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/database_migrations/20150612122233_table_constraints.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  change do\n    case ENV[\"HANAMI_DATABASE_TYPE\"]\n    when \"sqlite\"\n      create_table :table_constraints do\n        column :age, Integer\n        constraint(:age_constraint) { age > 18 }\n\n        column :role, String\n        check %(role IN(\"contributor\", \"manager\", \"owner\"))\n      end\n    when \"postgresql\"\n      create_table :table_constraints do\n        column :age, Integer\n        constraint(:age_constraint) { age > 18 }\n\n        column :role, String\n        check %(role IN('contributor', 'manager', 'owner'))\n      end\n    when \"mysql\"\n      create_table :table_constraints do\n        column :age, Integer\n        constraint(:age_constraint) { age > 18 }\n\n        column :role, String\n        check %(role IN(\"contributor\", \"manager\", \"owner\"))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/database_migrations/20150612124205_table_alterations.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  change do\n    case ENV[\"HANAMI_DATABASE_TYPE\"]\n    when \"sqlite\"\n      create_table :songs do\n        column :title, String\n        column :useless, String\n\n        foreign_key :artist_id, :artists\n        index :artist_id\n\n        add_constraint(:useless_min_length) { char_length(useless) > 2 }\n      end\n\n      alter_table :songs do\n        add_primary_key :id\n\n        add_column      :downloads_count, Integer\n        set_column_type :useless,         File\n\n        rename_column :title, :primary_title\n        set_column_default :primary_title, \"Unknown title\"\n\n        # add_index :album_id\n        # drop_index :artist_id\n\n        # add_foreign_key :album_id, :albums, on_delete: :cascade\n        # drop_foreign_key :artist_id\n\n        # add_constraint(:title_min_length) { char_length(title) > 2 }\n\n        # add_unique_constraint [:album_id, :title]\n\n        drop_constraint :useless_min_length\n        drop_column     :useless\n      end\n    when \"postgresql\"\n      create_table :songs do\n        column :title, String\n        column :useless, String\n\n        foreign_key :artist_id, :artists\n        index :artist_id\n\n        # add_constraint(:useless_min_length) { char_length(useless) > 2 }\n      end\n\n      alter_table :songs do\n        add_primary_key :id\n\n        add_column    :downloads_count, Integer\n        # set_column_type :useless, File\n\n        rename_column :title, :primary_title\n        set_column_default :primary_title, \"Unknown title\"\n\n        # add_index :album_id\n        # drop_index :artist_id\n\n        # add_foreign_key :album_id, :albums, on_delete: :cascade\n        # drop_foreign_key :artist_id\n\n        # add_constraint(:title_min_length) { char_length(title) > 2 }\n\n        # add_unique_constraint [:album_id, :title]\n\n        # drop_constraint :useless_min_length\n        drop_column :useless\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/database_migrations/20160830094800_create_users.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  change do\n    drop_table?   :users\n    create_table? :users do\n      primary_key :id\n      column :name,           String\n      column :email,          String\n      column :age,            Integer,   null: false, default: 19\n      column :comments_count, Integer,   null: false, default: 0\n      column :active,         TrueClass, null: false, default: true\n      column :created_at,     DateTime,  null: false\n      column :updated_at,     DateTime,  null: false\n\n      check { age > 18 }\n      constraint(:comments_count_constraint) { comments_count >= 0 }\n    end\n\n    add_index :users, :email, unique: true\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/database_migrations/20160830094851_create_authors.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  change do\n    drop_table?   :authors\n    create_table? :authors do\n      primary_key :id\n      column :name,       String\n      column :created_at, DateTime, null: false\n      column :updated_at, DateTime, null: false\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/database_migrations/20160830094941_create_books.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  change do\n    drop_table?   :books\n    create_table? :books do\n      primary_key :id\n      foreign_key :author_id, :authors, on_delete: :cascade\n      column :title,      String\n      column :on_sale,    TrueClass, null: false, default: false\n      column :created_at, DateTime,  null: false\n      column :updated_at, DateTime,  null: false\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/database_migrations/20160830095033_create_t_operator.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  change do\n    drop_table?   :t_operator\n    create_table? :t_operator do\n      primary_key :operator_id\n      column :s_name, String\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/database_migrations/20160905125728_create_source_files.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  change do\n    case Database.engine\n    when :postgresql\n      create_table :source_files do\n        column :id,         \"uuid\",   primary_key: true, default: Hanami::Model::Sql.function(:uuid_generate_v4)\n        column :name,       String,   null: false\n        column :languages,  \"text[]\"\n        column :metadata,   \"jsonb\", null: false\n        column :json_info,  \"json\"\n        column :content,    File,     null: false\n        column :created_at, DateTime, null: false\n        column :updated_at, DateTime, null: false\n      end\n    else\n      create_table :source_files do\n        primary_key :id\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/database_migrations/20160909150704_create_avatars.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  change do\n    drop_table?   :avatars\n    create_table? :avatars do\n      primary_key :id\n      foreign_key :user_id, :users, on_delete: :cascade, null: false, unique: true\n\n      column :url, String, null: false\n      column :created_at, DateTime\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/database_migrations/20161104143844_create_warehouses.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  change do\n    drop_table?   :warehouses\n    create_table? :warehouses do\n      primary_key :id\n      column :name,       String\n      column :code,       String\n      column :created_at, DateTime,  null: false\n      column :updated_at, DateTime,  null: false\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/database_migrations/20161114094644_create_products.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  change do\n    case Database.engine\n    when :postgresql\n      create_table :products do\n        primary_key :id\n        column :name,       String\n        column :categories, \"text[]\"\n      end\n    else\n      create_table :products do\n        primary_key :id\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/database_migrations/20170103142428_create_colors.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  change do\n    case Database.engine\n    when :postgresql\n      extension :pg_enum\n      create_enum :rainbow, %w[red orange yellow green blue indigo violet]\n\n      create_table :colors do\n        primary_key :id\n\n        column :name, :rainbow, null: false\n\n        column :created_at, DateTime, null: false\n        column :updated_at, DateTime, null: false\n      end\n    else\n      create_table :colors do\n        primary_key :id\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/database_migrations/20170124081339_create_labels.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  change do\n    create_table :labels do\n      column :id, Integer\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/database_migrations/20170517115243_create_tokens.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  change do\n    drop_table?   :tokens\n    create_table? :tokens do\n      primary_key :id\n      column :token, String\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/database_migrations/20170519172332_create_categories.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  change do\n    drop_table?   :categories\n    create_table? :categories do\n      primary_key :id\n      column :name, String\n    end\n    drop_table? :book_ontologies\n    create_table? :book_ontologies do\n      primary_key :id\n\n      foreign_key :book_id, :books, on_delete: :cascade, null: false\n      foreign_key :category_id, :categories, on_delete: :cascade, null: false\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/database_migrations/20171002201227_create_posts_and_comments.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  change do\n    drop_table?   :posts\n    create_table? :posts do\n      primary_key :id\n      column :title, String\n      foreign_key :user_id, :users, on_delete: :cascade, null: false\n    end\n    drop_table? :comments\n    create_table? :comments do\n      primary_key :id\n\n      foreign_key :user_id, :users, on_delete: :cascade, null: false\n      foreign_key :post_id, :posts, on_delete: :cascade, null: false\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/empty_migrations/.gitkeep",
    "content": ""
  },
  {
    "path": "spec/support/fixtures/migrations/20160831073534_create_reviews.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  up do\n    create_table :reviews do\n      primary_key :id\n      column :title, String, null: false\n    end\n  end\n\n  down do\n    drop_table :reviews\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures/migrations/20160831090612_add_rating_to_reviews.rb",
    "content": "# frozen_string_literal: true\n\nHanami::Model.migration do\n  up do\n    add_column :reviews, :rating, \"integer\", default: 0\n  end\n\n  down do\n    drop_column :reviews, :rating\n  end\nend\n"
  },
  {
    "path": "spec/support/fixtures.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"ostruct\"\n\nclass BaseParams < OpenStruct\n  def to_hash\n    to_h\n  end\nend\n\nclass User < Hanami::Entity\nend\n\nclass Avatar < Hanami::Entity\nend\n\nclass Author < Hanami::Entity\nend\n\nclass Book < Hanami::Entity\nend\n\nclass Category < Hanami::Entity\nend\n\nclass BookOntology < Hanami::Entity\nend\n\nclass Operator < Hanami::Entity\nend\n\nclass AccessToken < Hanami::Entity\nend\n\nclass SourceFile < Hanami::Entity\nend\n\nclass Post < Hanami::Entity\nend\n\nclass Comment < Hanami::Entity\nend\n\nclass Warehouse < Hanami::Entity\n  attributes do\n    attribute :id,   Types::Int\n    attribute :name, Types::String\n    attribute :code, Types::String.constrained(format: /\\Awh\\-/)\n  end\nend\n\nclass Account < Hanami::Entity\n  attributes do\n    attribute :id,         Types::Strict::Int\n    attribute :name,       Types::String\n    attribute :codes,      Types::Collection(Types::Coercible::Int)\n    attribute :owner,      Types::Entity(User)\n    attribute :users,      Types::Collection(User)\n    attribute :email,      Types::String.constrained(format: /@/)\n    attribute :created_at, Types::DateTime.constructor(->(dt) { ::DateTime.parse(dt.to_s) })\n  end\nend\n\nclass PageVisit < Hanami::Entity\n  attributes do\n    attribute :id,        Types::Strict::Int\n    attribute :start,     Types::DateTime\n    attribute :end,       Types::DateTime\n    attribute :visitor,   Types::Hash\n    attribute :page_info, Types::Hash.symbolized(\n      name: Types::Coercible::String,\n      scroll_depth: Types::Coercible::Float,\n      meta: Types::Hash\n    )\n  end\nend\n\nclass Person < Hanami::Entity\n  attributes :strict do\n    attribute :id,   Types::Strict::Int\n    attribute :name, Types::Strict::String\n  end\nend\n\nclass Product < Hanami::Entity\nend\n\nclass Color < Hanami::Entity\nend\n\nclass Label < Hanami::Entity\nend\n\nclass PostRepository < Hanami::Repository\n  associations do\n    belongs_to :user, as: :author\n    has_many :comments\n    has_many :users, through: :comments, as: :commenters\n  end\n\n  def find_with_commenters(id)\n    aggregate(:commenters).where(id: id).map_to(Post).to_a\n  end\n\n  def commenters_for(post)\n    assoc(:commenters, post).to_a\n  end\n\n  def find_with_author(id)\n    aggregate(:author).where(id: id).map_to(Post).one\n  end\n\n  def feed_for(id)\n    aggregate(:author, comments: :user).where(id: id).map_to(Post).one\n  end\n\n  def author_for(post)\n    assoc(:author, post).one\n  end\nend\n\nclass CommentRepository < Hanami::Repository\n  associations do\n    belongs_to :post\n    belongs_to :user\n  end\n\n  def commenter_for(comment)\n    assoc(:user, comment).one\n  end\nend\n\nclass AvatarRepository < Hanami::Repository\n  associations do\n    belongs_to :user\n  end\n\n  def by_user(id)\n    avatars.where(user_id: id).to_a\n  end\nend\n\nclass UserRepository < Hanami::Repository\n  associations do\n    has_one :avatar\n    has_many :posts, as: :threads\n    has_many :comments\n  end\n\n  def find_with_threads(id)\n    aggregate(:threads).where(id: id).map_to(User).one\n  end\n\n  def threads_for(user)\n    assoc(:threads, user).to_a\n  end\n\n  def find_with_avatar(id)\n    aggregate(:avatar).where(id: id).map_to(User).one\n  end\n\n  def create_with_avatar(data)\n    assoc(:avatar).create(data)\n  end\n\n  def remove_avatar(user)\n    assoc(:avatar, user).delete\n  end\n\n  def add_avatar(user, data)\n    assoc(:avatar, user).add(data)\n  end\n\n  def update_avatar(user, data)\n    assoc(:avatar, user).update(data)\n  end\n\n  def replace_avatar(user, data)\n    assoc(:avatar, user).replace(data)\n  end\n\n  def avatar_for(user)\n    assoc(:avatar, user).one\n  end\n\n  def by_name(name)\n    users.where(name: name)\n  end\n\n  def by_matching_name(name)\n    users.where(users[:name].ilike(name)).map_to(User).to_a\n  end\n\n  def by_name_with_root(name)\n    root.where(name: name).as(:entity)\n  end\n\n  def find_all_by_manual_query\n    users.read(\"select * from users\").to_a\n  end\n\n  def ids\n    users.select(:id).to_a\n  end\n\n  def select_id_and_name\n    users.select(:id, :name).to_a\n  end\nend\n\nclass AvatarRepository < Hanami::Repository\nend\n\nclass AuthorRepository < Hanami::Repository\n  associations do\n    has_many :books\n  end\n\n  def create_many(data, opts: {})\n    command(create: :authors, result: :many, **opts).call(data)\n  end\n\n  def create_with_books(data)\n    assoc(:books).create(data)\n  end\n\n  def find_with_books(id)\n    aggregate(:books).by_pk(id).map_to(Author).one\n  end\n\n  def books_for(author)\n    assoc(:books, author)\n  end\n\n  def add_book(author, data)\n    assoc(:books, author).add(data)\n  end\n\n  def remove_book(author, id)\n    assoc(:books, author).remove(id)\n  end\n\n  def delete_books(author)\n    assoc(:books, author).delete\n  end\n\n  def delete_on_sales_books(author)\n    assoc(:books, author).where(on_sale: true).delete\n  end\n\n  def books_count(author)\n    assoc(:books, author).count\n  end\n\n  def on_sales_books_count(author)\n    assoc(:books, author).where(on_sale: true).count\n  end\n\n  def find_book(author, id)\n    book_for(author, id).one\n  end\n\n  def book_exists?(author, id)\n    book_for(author, id).exists?\n  end\n\n  private\n\n  def book_for(author, id)\n    assoc(:books, author).where(id: id)\n  end\nend\n\nclass BookOntologyRepository < Hanami::Repository\n  associations do\n    belongs_to :books\n    belongs_to :categories\n  end\nend\n\nclass CategoryRepository < Hanami::Repository\n  associations do\n    has_many :books, through: :book_ontologies\n  end\n\n  def books_for(category)\n    assoc(:books, category)\n  end\n\n  def on_sales_books_count(category)\n    assoc(:books, category).where(on_sale: true).count\n  end\n\n  def books_count(category)\n    assoc(:books, category).count\n  end\n\n  def find_with_books(id)\n    aggregate(:books).where(id: id).map_to(Category).one\n  end\n\n  def add_books(category, *books)\n    assoc(:books, category).add(*books)\n  end\n\n  def remove_book(category, book_id)\n    assoc(:books, category).remove(book_id)\n  end\nend\n\nclass BookRepository < Hanami::Repository\n  associations do\n    belongs_to :author\n    has_many :categories, through: :book_ontologies\n  end\n\n  def add_category(book, category)\n    assoc(:categories, book).add(category)\n  end\n\n  def clear_categories(book)\n    assoc(:categories, book).delete\n  end\n\n  def categories_for(book)\n    assoc(:categories, book).to_a\n  end\n\n  def find_with_categories(id)\n    aggregate(:categories).where(id: id).map_to(Book).one\n  end\n\n  def find_with_author(id)\n    aggregate(:author).where(id: id).map_to(Book).one\n  end\n\n  def author_for(book)\n    assoc(:author, book).one\n  end\nend\n\nclass OperatorRepository < Hanami::Repository\n  self.relation = :t_operator\n\n  mapping do\n    attribute :id,   from: :operator_id\n    attribute :name, from: :s_name\n  end\nend\n\nclass AccessTokenRepository < Hanami::Repository\n  self.relation = \"tokens\"\nend\n\nclass SourceFileRepository < Hanami::Repository\nend\n\nclass WarehouseRepository < Hanami::Repository\nend\n\nclass ProductRepository < Hanami::Repository\nend\n\nclass ColorRepository < Hanami::Repository\n  schema do\n    attribute :id,         Hanami::Model::Sql::Types::Int\n    attribute :name,       Hanami::Model::Sql::Types::String\n    attribute :created_at, Hanami::Model::Sql::Types::DateTime\n    attribute :updated_at, Hanami::Model::Sql::Types::DateTime\n  end\nend\n\nclass LabelRepository < Hanami::Repository\nend\n\nHanami::Model.load!\n"
  },
  {
    "path": "spec/support/platform/ci.rb",
    "content": "# frozen_string_literal: true\n\nmodule Platform\n  module Ci\n    def self.ci?(name)\n      current == name\n    end\n\n    def self.current\n      if    travis? then :travis\n      elsif circle? then :circle\n      elsif drone?  then :drone\n      elsif github? then :github\n      end\n    end\n\n    class << self\n      private\n\n      def travis?\n        ENV[\"TRAVIS\"] == \"true\"\n      end\n\n      def circle?\n        ENV[\"CIRCLECI\"] == \"true\"\n      end\n\n      def drone?\n        ENV[\"DRONE\"] == \"true\"\n      end\n\n      def github?\n        ENV[\"GITHUB_ACTIONS\"] == \"true\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/platform/db.rb",
    "content": "# frozen_string_literal: true\n\nmodule Platform\n  module Db\n    def self.db?(name)\n      current == name\n    end\n\n    def self.current\n      Database.engine\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/platform/engine.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"hanami/utils\"\n\nmodule Platform\n  module Engine\n    def self.engine?(name)\n      current == name\n    end\n\n    def self.current\n      if    ruby?  then :ruby\n      elsif jruby? then :jruby\n      end\n    end\n\n    class << self\n      private\n\n      def ruby?\n        RUBY_ENGINE == \"ruby\"\n      end\n\n      def jruby?\n        Hanami::Utils.jruby?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/platform/matcher.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"hanami/utils/basic_object\"\n\nmodule Platform\n  class Matcher\n    class Nope < Hanami::Utils::BasicObject\n      def or(other, &blk)\n        blk.nil? ? other : blk.call\n      end\n\n      # rubocop:disable Style/MethodMissingSuper\n      # rubocop:disable Style/MissingRespondToMissing\n      def method_missing(*)\n        self.class.new\n      end\n      # rubocop:enable Style/MissingRespondToMissing\n      # rubocop:enable Style/MethodMissingSuper\n    end\n\n    def self.match(&blk)\n      catch :match do\n        new.__send__(:match, &blk)\n      end\n    end\n\n    def self.match?(os: Os.current, ci: Ci.current, engine: Engine.current, db: Db.current)\n      catch :match do\n        new.os(os).ci(ci).engine(engine).db(db) { true }.or(false)\n      end\n    end\n\n    def initialize\n      freeze\n    end\n\n    def os(name, &blk)\n      return nope unless os?(name)\n\n      block_given? ? resolve(&blk) : yep\n    end\n\n    def ci(name, &blk)\n      return nope unless ci?(name)\n\n      block_given? ? resolve(&blk) : yep\n    end\n\n    def engine(name, &blk)\n      return nope unless engine?(name)\n\n      block_given? ? resolve(&blk) : yep\n    end\n\n    def db(name, &blk)\n      return nope unless db?(name)\n\n      block_given? ? resolve(&blk) : yep\n    end\n\n    def default(&blk)\n      resolve(&blk)\n    end\n\n    private\n\n    def match(&blk)\n      instance_exec(&blk)\n    end\n\n    def nope\n      Nope.new\n    end\n\n    def yep\n      self.class.new\n    end\n\n    def resolve\n      throw :match, yield\n    end\n\n    def os?(name)\n      Os.os?(name)\n    end\n\n    def ci?(name)\n      Ci.ci?(name)\n    end\n\n    def engine?(name)\n      Engine.engine?(name)\n    end\n\n    def db?(name)\n      Db.db?(name)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/platform/os.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rbconfig\"\n\nmodule Platform\n  module Os\n    def self.os?(name)\n      current == name\n    end\n\n    def self.current\n      case RbConfig::CONFIG[\"host_os\"]\n      when /linux/  then :linux\n      when /darwin/ then :macos\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/platform.rb",
    "content": "# frozen_string_literal: true\n\nmodule Platform\n  require_relative \"platform/os\"\n  require_relative \"platform/ci\"\n  require_relative \"platform/engine\"\n  require_relative \"platform/db\"\n  require_relative \"platform/matcher\"\n\n  def self.ci?\n    !Ci.current.nil?\n  end\n\n  def self.match(&blk)\n    Matcher.match(&blk)\n  end\n\n  def self.match?(**args)\n    Matcher.match?(**args)\n  end\nend\n\nmodule PlatformHelpers\n  def with_platform(**args)\n    yield if Platform.match?(**args)\n  end\n\n  def unless_platform(**args)\n    yield unless Platform.match?(**args)\n  end\nend\n"
  },
  {
    "path": "spec/support/rspec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.configure do |config|\n  config.expect_with :rspec do |expectations|\n    expectations.include_chain_clauses_in_custom_matcher_descriptions = true\n  end\n\n  config.mock_with :rspec do |mocks|\n    mocks.verify_partial_doubles = true\n  end\n\n  config.shared_context_metadata_behavior = :apply_to_host_groups\n\n  config.filter_run_when_matching :focus\n  config.disable_monkey_patching!\n\n  config.warnings = true\n\n  config.default_formatter = \"doc\" if config.files_to_run.one?\n\n  config.profile_examples = 10\n\n  config.order = :random\n  Kernel.srand config.seed\nend\n"
  },
  {
    "path": "spec/support/test_io.rb",
    "content": "# frozen_string_literal: true\n\nmodule TestIO\n  def self.with_stdout\n    stdout = $stdout\n    $stdout = stream\n    yield\n  ensure\n    $stdout.close\n    $stdout = stdout\n  end\n\n  def self.stream\n    File.new(ENV[\"HANAMI_DATABASE_LOGGER\"], \"a+\")\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/entity/automatic_schema_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Entity do\n  describe \"automatic schema\" do\n    let(:described_class) { Author }\n\n    let(:input) do\n      Class.new do\n        def to_hash\n          Hash[id: 1]\n        end\n      end.new\n    end\n\n    describe \"#initialize\" do\n      it \"can be instantiated without attributes\" do\n        entity = described_class.new\n\n        expect(entity).to be_a_kind_of(described_class)\n      end\n\n      it \"accepts a hash\" do\n        entity = described_class.new(id: 1, name: \"Luca\", books: books = [Book.new], created_at: now = Time.now.utc)\n\n        expect(entity.id).to eq(1)\n        expect(entity.name).to eq(\"Luca\")\n        expect(entity.books).to eq(books)\n        expect(entity.created_at).to be_within(2).of(now)\n      end\n\n      it \"accepts object that implements #to_hash\" do\n        entity = described_class.new(input)\n\n        expect(entity.id).to eq(1)\n      end\n\n      it \"freezes the instance\" do\n        entity = described_class.new\n\n        expect(entity).to be_frozen\n      end\n\n      it \"coerces values\" do\n        now = Time.now\n        entity = described_class.new(created_at: now.to_s)\n\n        expect(entity.created_at).to be_within(2).of(now)\n      end\n\n      it \"coerces values for array of objects\" do\n        entity = described_class.new(books: books = [{title: \"TDD\"}, {title: \"Refactoring\"}])\n\n        books.each_with_index do |book, i|\n          b = entity.books[i]\n\n          expect(b).to be_a_kind_of(Book)\n          expect(b.title).to eq(book.fetch(:title))\n        end\n      end\n\n      it \"raises error if initialized with wrong array object\" do\n        object = Object.new\n        expect { described_class.new(books: [object]) }.to raise_error do |error|\n          expect(error).to be_a(TypeError)\n          expect(error.message).to include(\"[#<Object:0x\")\n          expect(error.message).to include(\">] (Array) has invalid type for :books\")\n        end\n      end\n    end\n\n    describe \"#id\" do\n      it \"returns the value\" do\n        entity = described_class.new(id: 1)\n\n        expect(entity.id).to eq(1)\n      end\n\n      it \"returns nil if not present in attributes\" do\n        entity = described_class.new\n\n        expect(entity.id).to be_nil\n      end\n    end\n\n    describe \"accessors\" do\n      it \"exposes accessors from schema\" do\n        entity = described_class.new(name: \"Luca\")\n\n        expect(entity.name).to eq(\"Luca\")\n      end\n\n      it \"raises error for unknown methods\" do\n        entity = described_class.new\n\n        expect { entity.foo }\n          .to raise_error(NoMethodError, /undefined method `foo'/)\n      end\n\n      it \"raises error when #attributes is invoked\" do\n        entity = described_class.new\n\n        expect { entity.attributes }\n          .to raise_error(NoMethodError, /private method `attributes' called for #<Author/)\n      end\n    end\n\n    describe \"#to_h\" do\n      it \"serializes attributes into hash\" do\n        entity = described_class.new(id: 1, name: \"Luca\")\n\n        expect(entity.to_h).to eq(Hash[id: 1, name: \"Luca\"])\n      end\n\n      it \"must be an instance of ::Hash\" do\n        entity = described_class.new\n\n        expect(entity.to_h).to be_an_instance_of(::Hash)\n      end\n\n      it \"ignores unknown attributes\" do\n        entity = described_class.new(foo: \"bar\")\n\n        expect(entity.to_h).to eq(Hash[])\n      end\n\n      it \"prevents information escape\" do\n        entity = described_class.new(books: books = [Book.new(id: 1), Book.new(id: 2)])\n\n        entity.to_h[:books].reverse!\n        expect(entity.books).to eq(books)\n      end\n\n      it \"is aliased as #to_hash\" do\n        entity = described_class.new(name: \"Luca\")\n\n        expect(entity.to_hash).to eq(entity.to_h)\n      end\n    end\n\n    describe \"#respond_to?\" do\n      it \"returns ture for id\" do\n        entity = described_class.new\n\n        expect(entity).to respond_to(:id)\n      end\n\n      it \"returns true for methods with the same name of attributes defined by schema\" do\n        entity = described_class.new\n\n        expect(entity).to respond_to(:name)\n      end\n\n      it \"returns false for methods not in the set of attributes defined by schema\" do\n        entity = described_class.new(foo: \"bar\")\n\n        expect(entity).to_not respond_to(:foo)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/entity/manual_schema/base_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Entity do\n  describe \"manual schema (base)\" do\n    let(:described_class) { Account }\n\n    let(:input) do\n      Class.new do\n        def to_hash\n          Hash[id: 1]\n        end\n      end.new\n    end\n\n    describe \"#initialize\" do\n      it \"can be instantiated without attributes\" do\n        entity = described_class.new\n\n        expect(entity).to be_a_kind_of(described_class)\n      end\n\n      it \"accepts a hash\" do\n        entity = described_class.new(id: 1, owner: owner = User.new(name: \"MG\"), users: users = [User.new], name: \"Acme Inc.\", codes: [1, 2, 3], email: \"account@acme-inc.test\", created_at: now = DateTime.now)\n\n        expect(entity.id).to eq(1)\n        expect(entity.name).to eq(\"Acme Inc.\")\n        expect(entity.owner).to eq(owner)\n        expect(entity.users).to eq(users)\n        expect(entity.codes).to eq([1, 2, 3])\n        expect(entity.email).to eq(\"account@acme-inc.test\")\n        expect(entity.created_at).to be_within(1).of(now)\n      end\n\n      it \"accepts object that implements #to_hash\" do\n        entity = described_class.new(input)\n\n        expect(entity.id).to eq(1)\n      end\n\n      it \"freezes the instance\" do\n        entity = described_class.new\n\n        expect(entity).to be_frozen\n      end\n\n      it \"coerces values\" do\n        now = DateTime.now\n        entity = described_class.new(created_at: now.to_s)\n\n        expect(entity.created_at).to be_a_kind_of(DateTime)\n        expect(entity.created_at).to be_within(1).of(now)\n      end\n\n      it \"coerces values for array of primitives\" do\n        entity = described_class.new(codes: %w[4 5 6])\n\n        expect(entity.codes).to eq([4, 5, 6])\n      end\n\n      it \"coerces values for single object\" do\n        entity = described_class.new(owner: owner = {name: \"L\"})\n\n        expect(entity.owner).to be_a_kind_of(User)\n        expect(entity.owner.name).to eq(owner.fetch(:name))\n      end\n\n      it \"coerces values for array of objects\" do\n        entity = described_class.new(users: users = [{name: \"L\"}, {name: \"MG\"}])\n\n        users.each_with_index do |user, i|\n          u = entity.users[i]\n\n          expect(u).to be_a_kind_of(User)\n          expect(u.name).to eq(user.fetch(:name))\n        end\n      end\n\n      it \"raises error if initialized with wrong primitive\" do\n        expect { described_class.new(id: :foo) }\n          .to raise_error(TypeError, \":foo (Symbol) has invalid type for :id violates constraints (type?(Integer, :foo) failed)\")\n      end\n\n      it \"raises error if initialized with wrong array primitive\" do\n        message = Platform.match do\n          engine(:jruby) { \"no implicit conversion of Object into Integer\" }\n          default        { \"can't convert Object into Integer\" }\n        end\n\n        expect { described_class.new(codes: [Object.new]) }.to raise_error(TypeError, message)\n      end\n\n      it \"raises error if type constraint isn't honored\" do\n        expect { described_class.new(email: \"test\") }\n          .to raise_error(TypeError, '\"test\" (String) has invalid type for :email violates constraints (format?(/@/, \"test\") failed)')\n      end\n\n      it \"doesn't override manual defined schema\" do\n        expect { Warehouse.new(code: \"foo\") }\n          .to raise_error(TypeError, '\"foo\" (String) has invalid type for :code violates constraints (format?(/\\Awh\\-/, \"foo\") failed)')\n      end\n\n      it \"symbolizes nested hash keys according to schema\" do\n        entity = PageVisit.new(\n          id: 42,\n          start: DateTime.now,\n          end: (Time.now + 53).to_datetime,\n          visitor: {\n            \"user_agent\" => \"w3m/0.5.3\", \"language\" => {\"en\" => 0.9}\n          },\n          page_info: {\n            \"name\" => \"landing page\",\n            scroll_depth: 0.7,\n            \"meta\" => {\"version\" => \"0.8.3\", updated_at: 1_492_769_467_000}\n          }\n        )\n\n        expect(entity.visitor).to eq(\n          user_agent: \"w3m/0.5.3\", language: {en: 0.9}\n        )\n        expect(entity.page_info).to eq(\n          name: \"landing page\",\n          scroll_depth: 0.7,\n          meta: {version: \"0.8.3\", updated_at: 1_492_769_467_000}\n        )\n      end\n    end\n\n    describe \"#id\" do\n      it \"returns the value\" do\n        entity = described_class.new(id: 1)\n\n        expect(entity.id).to eq(1)\n      end\n\n      it \"returns nil if not present in attributes\" do\n        entity = described_class.new\n\n        expect(entity.id).to be_nil\n      end\n    end\n\n    describe \"accessors\" do\n      it \"exposes accessors from schema\" do\n        entity = described_class.new(name: \"Acme Inc.\")\n\n        expect(entity.name).to eq(\"Acme Inc.\")\n      end\n\n      it \"raises error for unknown methods\" do\n        entity = described_class.new\n\n        expect { entity.foo }\n          .to raise_error(NoMethodError, /undefined method `foo'/)\n      end\n\n      it \"raises error when #attributes is invoked\" do\n        entity = described_class.new\n\n        expect { entity.attributes }\n          .to raise_error(NoMethodError, /private method `attributes' called for #<Account/)\n      end\n    end\n\n    describe \"#to_h\" do\n      it \"serializes attributes into hash\" do\n        entity = described_class.new(id: 1, name: \"Acme Inc.\")\n\n        expect(entity.to_h).to eq(Hash[id: 1, name: \"Acme Inc.\"])\n      end\n\n      it \"must be an instance of ::Hash\" do\n        entity = described_class.new\n\n        expect(entity.to_h).to be_an_instance_of(::Hash)\n      end\n\n      it \"ignores unknown attributes\" do\n        entity = described_class.new(foo: \"bar\")\n\n        expect(entity.to_h).to eq(Hash[])\n      end\n\n      it \"prevents information escape\" do\n        entity = described_class.new(users: users = [User.new(id: 1), User.new(id: 2)])\n\n        entity.to_h[:users].reverse!\n        expect(entity.users).to eq(users)\n      end\n\n      it \"is aliased as #to_hash\" do\n        entity = described_class.new(name: \"Acme Inc.\")\n\n        expect(entity.to_hash).to eq(entity.to_h)\n      end\n    end\n\n    describe \"#respond_to?\" do\n      it \"returns ture for id\" do\n        entity = described_class.new\n\n        expect(entity).to respond_to(:id)\n      end\n\n      it \"returns true for methods with the same name of attributes defined by schema\" do\n        entity = described_class.new\n\n        expect(entity).to respond_to(:name)\n      end\n\n      it \"returns false for methods not in the set of attributes defined by schema\" do\n        entity = described_class.new(foo: \"bar\")\n\n        expect(entity).to_not respond_to(:foo)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/entity/manual_schema/strict_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Entity do\n  describe \"manual schema (strict)\" do\n    let(:described_class) { Person }\n\n    let(:input) do\n      Class.new do\n        def to_hash\n          Hash[id: 2, name: \"MG\"]\n        end\n      end.new\n    end\n\n    describe \"#initialize\" do\n      it \"can't be instantiated without attributes\" do\n        expect { described_class.new }.to raise_error(ArgumentError, \":id is missing in Hash input\")\n      end\n\n      it \"can't be instantiated with empty hash\" do\n        expect { described_class.new({}) }.to raise_error(ArgumentError, \":id is missing in Hash input\")\n      end\n\n      it \"can't be instantiated with partial data\" do\n        expect { described_class.new(id: 1) }.to raise_error(ArgumentError, \":name is missing in Hash input\")\n      end\n\n      it \"can't be instantiated with unknown data\" do\n        expect { described_class.new(id: 1, name: \"Luca\", foo: \"bar\") }.to raise_error(ArgumentError, \"unexpected keys [:foo] in Hash input\")\n      end\n\n      it \"can be instantiated with full data\" do\n        entity = described_class.new(id: 1, name: \"Luca\")\n\n        expect(entity.id).to   eq(1)\n        expect(entity.name).to eq(\"Luca\")\n      end\n\n      it \"accepts object that implements #to_hash\" do\n        entity = described_class.new(input)\n\n        expect(entity.id).to   eq(2)\n        expect(entity.name).to eq(\"MG\")\n      end\n\n      it \"freezes the instance\" do\n        entity = described_class.new(id: 1, name: \"Luca\")\n\n        expect(entity).to be_frozen\n      end\n\n      it \"fails if values aren't of the expected type\" do\n        expect { described_class.new(id: \"1\", name: \"Luca\") }.to raise_error(TypeError, %(\"1\" (String) has invalid type for :id violates constraints (type?(Integer, \"1\") failed)))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/entity/manual_schema/types_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Entity do\n  describe \"manual schema (types)\" do\n    [nil, :schema, :strict, :weak, :permissive, :strict_with_defaults, :symbolized].each do |type|\n      it \"allows to build schema with #{type.inspect}\" do\n        Class.new(described_class) do\n          attributes(type) {}\n        end\n      end\n    end\n\n    it \"raises error for unknown type\" do\n      expect do\n        Class.new(described_class) do\n          attributes(:unknown) {}\n        end\n      end.to raise_error(Hanami::Model::Error, \"Unknown schema type: `:unknown'\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/entity/schema/definition_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Entity::Schema::Definition do\n  let(:described_class) { Hanami::Entity::Schema::Definition }\n  let(:subject) do\n    described_class.new do\n      attribute :id, Hanami::Model::Types::Coercible::Int\n    end\n  end\n\n  describe \"#initialize\" do\n    it \"returns frozen instance\" do\n      subject = described_class.new {}\n\n      expect(subject).to be_frozen\n    end\n\n    it \"raises error if block isn't given\" do\n      expect { described_class.new }.to raise_error(LocalJumpError)\n    end\n  end\n\n  describe \"#call\" do\n    it \"returns empty hash when nil is given\" do\n      result = subject.call(nil)\n\n      expect(result).to eq({})\n    end\n\n    it \"processes attributes\" do\n      result = subject.call(id: 1)\n\n      expect(result).to eq(id: 1)\n    end\n\n    it \"ignores unknown attributes\" do\n      result = subject.call(foo: \"bar\")\n\n      expect(result).to eq({})\n    end\n\n    it \"raises error if the process fails\" do\n      message = Platform.match do\n        engine(:jruby) { \"no implicit conversion of Symbol into Integer\" }\n        default        { \"can't convert Symbol into Integer\" }\n      end\n\n      expect { subject.call(id: :foo) }.to raise_error(TypeError, message)\n    end\n  end\n\n  describe \"#attribute?\" do\n    it \"returns true for known attributes\" do\n      expect(subject.attribute?(:id)).to eq(true)\n    end\n\n    it \"returns false for unknown attributes\" do\n      expect(subject.attribute?(:foo)).to eq(false)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/entity/schema/schemaless_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Entity::Schema::Schemaless do\n  let(:subject) { Hanami::Entity::Schema::Schemaless.new }\n\n  describe \"#initialize\" do\n    it \"returns frozen instance\" do\n      expect(subject).to be_frozen\n    end\n  end\n\n  describe \"#call\" do\n    it \"returns empty hash when nil is given\" do\n      result = subject.call(nil)\n\n      expect(result).to eq({})\n    end\n\n    it \"returns duped hash\" do\n      input = {foo: \"bar\"}\n      result = subject.call(input)\n\n      expect(result).to eq(input)\n      expect(result.object_id).to_not eq(input.object_id)\n    end\n  end\n\n  describe \"#attribute?\" do\n    it \"always returns true\" do\n      expect(subject.attribute?(:foo)).to eq(true)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/entity/schema_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Entity::Schema do\n  let(:described_class) { Hanami::Entity::Schema }\n\n  describe \"without definition\" do\n    let(:subject) { described_class.new }\n\n    describe \"#call\" do\n      it \"processes attributes\" do\n        result = subject.call(\"foo\" => \"bar\")\n\n        expect(result).to eq(foo: \"bar\")\n      end\n    end\n\n    describe \"#attribute?\" do\n      it \"always returns true\" do\n        expect(subject.attribute?(:foo)).to eq true\n      end\n    end\n  end\n\n  describe \"with definition\" do\n    let(:subject) do\n      described_class.new do\n        attribute :id, Hanami::Model::Types::Coercible::Int\n      end\n    end\n\n    describe \"#call\" do\n      it \"processes attributes\" do\n        result = subject.call(id: \"1\")\n\n        expect(result).to eq(id: 1)\n      end\n\n      it \"ignores unknown attributes\" do\n        result = subject.call(foo: \"bar\")\n\n        expect(result).to eq({})\n      end\n    end\n\n    describe \"#attribute?\" do\n      it \"returns true for known attributes\" do\n        expect(subject.attribute?(:id)).to eq true\n      end\n\n      it \"returns false for unknown attributes\" do\n        expect(subject.attribute?(:foo)).to eq false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/entity/schemaless_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Entity do\n  describe \"schemaless\" do\n    let(:described_class) do\n      Class.new(Hanami::Entity)\n    end\n\n    let(:input) do\n      Class.new do\n        def to_hash\n          Hash[a: 1]\n        end\n      end.new\n    end\n\n    describe \"#initialize\" do\n      it \"can be instantiated without attributes\" do\n        entity = described_class.new\n\n        expect(entity).to be_a_kind_of(described_class)\n      end\n\n      it \"accepts a hash\" do\n        entity = described_class.new(foo: 1, \"bar\" => 2)\n\n        expect(entity.foo).to eq(1)\n        expect(entity.bar).to eq(2)\n      end\n\n      it \"accepts object that implements #to_hash\" do\n        entity = described_class.new(input)\n\n        expect(entity.a).to eq(1)\n      end\n\n      it \"freezes the instance\" do\n        entity = described_class.new\n\n        expect(entity).to be_frozen\n      end\n    end\n\n    describe \"#id\" do\n      it \"returns the value\" do\n        entity = described_class.new(id: 1)\n\n        expect(entity.id).to eq(1)\n      end\n\n      it \"returns nil if not present in attributes\" do\n        entity = described_class.new\n\n        expect(entity.id).to be_nil\n      end\n    end\n\n    describe \"accessors\" do\n      it \"exposes accessors for given keys\" do\n        entity = described_class.new(name: \"Luca\")\n\n        expect(entity.name).to eq(\"Luca\")\n      end\n\n      it \"returns nil for unknown methods\" do\n        entity = described_class.new\n\n        expect(entity.foo).to be_nil\n      end\n\n      it \"returns nil for #attributes\" do\n        entity = described_class.new\n\n        expect(entity.attributes).to be_nil\n      end\n    end\n\n    describe \"#to_h\" do\n      it \"serializes attributes into hash\" do\n        entity = described_class.new(foo: 1, \"bar\" => {\"baz\" => 2})\n\n        expect(entity.to_h).to eq(Hash[foo: 1, bar: {baz: 2}])\n      end\n\n      it \"must be an instance of ::Hash\" do\n        entity = described_class.new\n\n        expect(entity.to_h).to be_an_instance_of(::Hash)\n      end\n\n      it \"prevents information escape\" do\n        entity = described_class.new(a: [1, 2, 3])\n\n        entity.to_h[:a].reverse!\n        expect(entity.a).to eq([1, 2, 3])\n      end\n\n      it \"is aliased as #to_hash\" do\n        entity = described_class.new(foo: \"bar\")\n\n        expect(entity.to_h).to eq(entity.to_hash)\n      end\n    end\n\n    describe \"#respond_to?\" do\n      it \"returns ture for id\" do\n        entity = described_class.new\n\n        expect(entity).to respond_to(:id)\n      end\n\n      it \"returns true for present keys\" do\n        entity = described_class.new(foo: 1, \"bar\" => 2)\n\n        expect(entity).to respond_to(:foo)\n        expect(entity).to respond_to(:bar)\n      end\n\n      it \"returns false for missing keys\" do\n        entity = described_class.new\n\n        expect(entity).to respond_to(:baz)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/entity_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"ostruct\"\n\nRSpec.describe Hanami::Entity do\n  let(:described_class) do\n    Class.new(Hanami::Entity)\n  end\n\n  describe \"equality\" do\n    it \"returns true if same class and same id\" do\n      entity1 = described_class.new(id: 1)\n      entity2 = described_class.new(id: 1)\n\n      expect(entity1).to eq(entity2), \"Expected #{entity1.inspect} to equal #{entity2.inspect}\"\n    end\n\n    it \"returns false if same class but different id\" do\n      entity1 = described_class.new(id: 1)\n      entity2 = described_class.new(id: 1000)\n\n      expect(entity1).to_not eq(entity2), \"Expected #{entity1.inspect} to NOT equal #{entity2.inspect}\"\n    end\n\n    it \"returns false if different class but same id\" do\n      entity1 = described_class.new(id: 1)\n      entity2 = OpenStruct.new(id: 1)\n\n      expect(entity1).to_not eq(entity2), \"Expected #{entity1.inspect} to NOT equal #{entity2.inspect}\"\n    end\n\n    it \"returns false if different class and different id\" do\n      entity1 = described_class.new(id: 1)\n      entity2 = OpenStruct.new(id: 1000)\n\n      expect(entity1).to_not eq(entity2), \"Expected #{entity1.inspect} to NOT equal #{entity2.inspect}\"\n    end\n\n    it \"returns true when both the ids are nil\" do\n      entity1 = described_class.new\n      entity2 = described_class.new\n\n      expect(entity1).to eq(entity2), \"Expected #{entity1.inspect} to equal #{entity2.inspect}\"\n    end\n  end\n\n  describe \"#hash\" do\n    it \"returns predictable object hashing\" do\n      entity1 = described_class.new(id: 1)\n      entity2 = described_class.new(id: 1)\n\n      expect(entity1.hash).to eq(entity2.hash), \"Expected #{entity1.hash} to equal #{entity2.hash}\"\n    end\n\n    it \"returns different object hash for same class but different id\" do\n      entity1 = described_class.new(id: 1)\n      entity2 = described_class.new(id: 1000)\n\n      expect(entity1.hash).to_not eq(entity2.hash), \"Expected #{entity1.hash} to NOT equal #{entity2.hash}\"\n    end\n\n    it \"returns different object hash for different class but same id\" do\n      entity1 = described_class.new(id: 1)\n      entity2 = Class.new(Hanami::Entity).new(id: 1)\n\n      expect(entity1.hash).to_not eq(entity2.hash), \"Expected #{entity1.hash} to NOT equal #{entity2.hash}\"\n    end\n\n    it \"returns different object hash for different class and different id\" do\n      entity1 = described_class.new(id: 1)\n      entity2 = Class.new(Hanami::Entity).new(id: 2)\n\n      expect(entity1.hash).to_not eq(entity2.hash), \"Expected #{entity1.hash} to NOT equal #{entity2.hash}\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/check_constraint_validation_error_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::CheckConstraintViolationError do\n  it \"inherits from Hanami::Model::ConstraintViolationError\" do\n    expect(described_class.ancestors).to include(Hanami::Model::ConstraintViolationError)\n  end\n\n  it \"has a default error message\" do\n    expect { raise described_class }.to raise_error(described_class, \"Check constraint has been violated\")\n  end\n\n  it \"allows custom error message\" do\n    expect { raise described_class.new(\"Ouch\") }.to raise_error(described_class, \"Ouch\")\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/configuration_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::Configuration do\n  before do\n    database_directory = Pathname.pwd.join(\"tmp\", \"db\")\n    database_directory.join(\"migrations\").mkpath\n\n    FileUtils.touch database_directory.join(\"schema.sql\")\n  end\n\n  let(:subject) { Hanami::Model::Configuration.new(configurator) }\n\n  let(:configurator) do\n    adapter_url = url\n\n    Hanami::Model::Configurator.build do\n      adapter :sql, adapter_url\n\n      migrations \"tmp/db/migrations\"\n      schema     \"tmp/db/schema.sql\"\n    end\n  end\n\n  let(:url) do\n    db = \"tmp/db/bookshelf.sqlite\"\n\n    Platform.match do\n      engine(:ruby)  { \"sqlite://#{db}\" }\n      engine(:jruby) { \"jdbc:sqlite://#{db}\" }\n    end\n  end\n\n  describe \"#url\" do\n    it \"equals to the configured url\" do\n      expect(subject.url).to eq(url)\n    end\n  end\n\n  describe \"#connection\" do\n    it \"returns a raw connection aganist the database\" do\n      connection = subject.connection\n\n      expect(connection).to be_a_kind_of(Sequel::Database)\n      expect(connection.url).to eq(url)\n    end\n\n    context \"with blank url\" do\n      let(:url) { nil }\n\n      it \"raises error\" do\n        expect { subject.connection }.to raise_error(Hanami::Model::UnknownDatabaseAdapterError, \"Unknown database adapter for URL: #{url.inspect}. Please check your database configuration (hint: ENV['DATABASE_URL']).\")\n      end\n    end\n  end\n\n  describe \"#gateway\" do\n    it \"returns default ROM gateway\" do\n      gateway = subject.gateway\n\n      expect(gateway).to be_a_kind_of(ROM::Gateway)\n      expect(gateway.connection).to eq(subject.connection)\n    end\n\n    context \"with blank url\" do\n      let(:url) { nil }\n\n      it \"raises error\" do\n        expect { subject.connection }.to raise_error(Hanami::Model::UnknownDatabaseAdapterError, \"Unknown database adapter for URL: #{url.inspect}. Please check your database configuration (hint: ENV['DATABASE_URL']).\")\n      end\n    end\n  end\n\n  describe \"#root\" do\n    it \"returns current directory\" do\n      expect(subject.root).to eq(Pathname.pwd)\n    end\n  end\n\n  describe \"#migrations\" do\n    it \"returns path to migrations\" do\n      expected = subject.root.join(\"tmp\", \"db\", \"migrations\")\n\n      expect(subject.migrations).to eq(expected)\n    end\n  end\n\n  describe \"#schema\" do\n    it \"returns path to database schema\" do\n      expected = subject.root.join(\"tmp\", \"db\", \"schema.sql\")\n\n      expect(subject.schema).to eq(expected)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/constraint_violation_error_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::ConstraintViolationError do\n  it \"inherits from Hanami::Model::Error\" do\n    expect(described_class.ancestors).to include(Hanami::Model::Error)\n  end\n\n  it \"has a default error message\" do\n    expect { raise described_class }.to raise_error(described_class, \"Constraint has been violated\")\n  end\n\n  it \"allows custom error message\" do\n    expect { raise described_class.new(\"Ouch\") }.to raise_error(described_class, \"Ouch\")\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/disconnect_spec.rb",
    "content": "# frozen_string_literal: true\n\n# This test is tightly coupled to Sequel\n#\n# We should improve connection management via ROM\nRSpec.describe \"Hanami::Model.disconnect\" do\n  before do\n    # warm up\n    connection[:users].to_a\n  end\n\n  let(:connection) { Hanami::Model.configuration.connection }\n\n  it \"disconnects from database\" do\n    # Sequel returns a collection of SQLite3::Database instances that were\n    # active and has been disconnected from the database\n    connections = Hanami::Model.disconnect\n    expect(connections.size).to eq(1)\n\n    # If we don't hit the database, the next disconnection returns an empty set\n    # of SQLite3::Database\n    connections = Hanami::Model.disconnect\n    expect(connections.size).to eq(0)\n\n    # If we try to use the database again, it's able to transparently reconnect\n    expect(connection[:users].to_a).to be_a_kind_of(Array)\n\n    # Now that we hit the database again, on this time the collection of\n    # disconnected SQLite3::Database instances has size of 1\n    connections = Hanami::Model.disconnect\n    expect(connections.size).to eq(1)\n  end\n\n  it \"doesn't disconnect from the database when not connected yet\" do\n    expect(connection).to receive(:disconnect)\n\n    Hanami::Model.disconnect\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/error_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::Error do\n  it \"inherits from StandardError\" do\n    expect(described_class.ancestors).to include(StandardError)\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/foreign_key_constraint_violation_error_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::ForeignKeyConstraintViolationError do\n  it \"inherits from Hanami::Model::ConstraintViolationError\" do\n    expect(described_class.ancestors).to include(Hanami::Model::ConstraintViolationError)\n  end\n\n  it \"has a default error message\" do\n    expect { raise described_class }.to raise_error(described_class, \"Foreign key constraint has been violated\")\n  end\n\n  it \"allows custom error message\" do\n    expect { raise described_class.new(\"Ouch\") }.to raise_error(described_class, \"Ouch\")\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/load_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model.load!\" do\n  let(:message) { \"Cannot find corresponding type for form\" }\n\n  before do\n    allow(ROM).to receive(:container) { raise ROM::SQL::UnknownDBTypeError, message }\n  end\n\n  it \"raises unknown database error when repository automapping spots an unknown type\" do\n    expect { Hanami::Model.load! }.to raise_error(Hanami::Model::UnknownDatabaseTypeError, message)\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/mapped_relation_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::MappedRelation do\n  subject { described_class.new(relation) }\n  let(:relation) { UserRepository.new.users }\n\n  describe \"#[]\" do\n    it \"returns attribute\" do\n      expect(subject[:name]).to be_a_kind_of(ROM::SQL::Attribute)\n    end\n\n    it \"raises error in case of unknown attribute\" do\n      expect { subject[:foo] }.to raise_error(Hanami::Model::UnknownAttributeError, \":foo attribute doesn't exist in users schema\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/migrator/adapter_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::Migrator::Adapter do\n  extend PlatformHelpers\n\n  subject { described_class.new(connection) }\n\n  let(:connection)    { instance_double(\"Hanami::Model::Migrator::Connection\", database_type: database_type) }\n  let(:database_type) { \"unknown\" }\n\n  describe \".for\" do\n    before do\n      expect(configuration).to receive(:url).at_least(:once).and_return(url)\n    end\n\n    let(:configuration) { instance_double(\"Hanami::Model::Configuration\") }\n    let(:url)           { ENV[\"HANAMI_DATABASE_URL\"] }\n\n    with_platform(db: :sqlite) do\n      context \"when sqlite\" do\n        it \"returns sqlite adapter\" do\n          expect(described_class.for(configuration)).to be_kind_of(Hanami::Model::Migrator::SQLiteAdapter)\n        end\n      end\n    end\n\n    with_platform(db: :postgresql) do\n      context \"when postgresql\" do\n        it \"returns postgresql adapter\" do\n          expect(described_class.for(configuration)).to be_kind_of(Hanami::Model::Migrator::PostgresAdapter)\n        end\n      end\n    end\n\n    with_platform(db: :mysql) do\n      context \"when mysql\" do\n        it \"returns mysql adapter\" do\n          expect(described_class.for(configuration)).to be_kind_of(Hanami::Model::Migrator::MySQLAdapter)\n        end\n      end\n    end\n\n    context \"when unknown\" do\n      let(:url) { \"unknown\" }\n\n      it \"returns generic adapter\" do\n        expect(described_class.for(configuration)).to be_kind_of(described_class)\n      end\n    end\n  end\n\n  describe \"#create\" do\n    it \"raises migration error\" do\n      expect { subject.create }.to raise_error(Hanami::Model::MigrationError, \"Current adapter (#{database_type}) doesn't support create.\")\n    end\n  end\n\n  describe \"#drop\" do\n    it \"raises migration error\" do\n      expect { subject.drop }.to raise_error(Hanami::Model::MigrationError, \"Current adapter (#{database_type}) doesn't support drop.\")\n    end\n  end\n\n  describe \"#load\" do\n    it \"raises migration error\" do\n      expect { subject.load }.to raise_error(Hanami::Model::MigrationError, \"Current adapter (#{database_type}) doesn't support load.\")\n    end\n  end\n\n  describe \"migrate\" do\n    it \"raises migration error in case of error\" do\n      expect(connection).to receive(:raw)\n      expect(Sequel::Migrator).to receive(:run).and_raise(Sequel::Migrator::Error.new(\"ouch\"))\n\n      expect { subject.migrate([], \"-1\") }.to raise_error(Hanami::Model::MigrationError, \"ouch\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/migrator/connection_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::Migrator::Connection do\n  extend PlatformHelpers\n\n  let(:connection) { Hanami::Model::Migrator::Connection.new(hanami_model_configuration) }\n\n  describe \"when not a jdbc connection\" do\n    let(:hanami_model_configuration) { OpenStruct.new(url: url) }\n    let(:url) { \"postgresql://postgres:s3cr3T@127.0.0.1:5432/database\" }\n\n    describe \"#jdbc?\" do\n      it \"returns false\" do\n        expect(connection.jdbc?).to eq(false)\n      end\n    end\n\n    describe \"#global_uri\" do\n      it \"returns connection URI without database\" do\n        expect(connection.global_uri.scan(\"database\").empty?).to eq(true)\n      end\n    end\n\n    describe \"#parsed_uri?\" do\n      it \"returns an URI instance\" do\n        expect(connection.parsed_uri).to be_a_kind_of(URI)\n      end\n    end\n\n    describe \"#host\" do\n      describe \"when the host is only specified in the URI\" do\n        let(:url) { \"postgresql://127.0.0.1/database\" }\n\n        it \"returns configured host\" do\n          expect(connection.host).to eq(\"127.0.0.1\")\n        end\n      end\n\n      describe \"when the host is only specified in the query\" do\n        let(:url) { \"postgresql:///database?host=0.0.0.0\" }\n\n        it \"returns the host specified in the query param\" do\n          expect(connection.host).to eql(\"0.0.0.0\")\n        end\n      end\n\n      describe \"when the host is specified as a socket\" do\n        let(:url) { \"postgresql:///database?host=/path/to/my/sock\" }\n\n        it \"returns the path to the socket specified in the query param\" do\n          expect(connection.host).to eql(\"/path/to/my/sock\")\n        end\n      end\n\n      describe \"when the host is specified in both the URI and query\" do\n        let(:url) { \"postgresql://127.0.0.1/database?host=0.0.0.0\" }\n\n        it \"prefers the host from the URI\" do\n          expect(connection.host).to eql(\"127.0.0.1\")\n        end\n      end\n    end\n\n    describe \"#port\" do\n      it \"returns configured port\" do\n        expect(connection.port).to eq(5432)\n      end\n    end\n\n    describe \"#database\" do\n      it \"returns configured database\" do\n        expect(connection.database).to eq(\"database\")\n      end\n    end\n\n    describe \"#user\" do\n      it \"returns configured user\" do\n        expect(connection.user).to eq(\"postgres\")\n      end\n\n      describe \"when there is no user option\" do\n        let(:hanami_model_configuration) do\n          OpenStruct.new(url: \"postgresql://127.0.0.1:5432/database\")\n        end\n\n        it \"returns nil\" do\n          expect(connection.user).to be_nil\n        end\n      end\n    end\n\n    describe \"#password\" do\n      it \"returns configured password\" do\n        expect(connection.password).to eq(\"s3cr3T\")\n      end\n\n      describe \"when there is no password option\" do\n        let(:hanami_model_configuration) do\n          OpenStruct.new(url: \"postgresql://127.0.0.1/database\")\n        end\n\n        it \"returns nil\" do\n          expect(connection.password).to be_nil\n        end\n      end\n    end\n\n    describe \"#raw\" do\n      let(:url) { ENV[\"HANAMI_DATABASE_URL\"] }\n\n      with_platform(db: :sqlite) do\n        context \"when sqlite\" do\n          it \"returns raw sequel connection\" do\n            expected = Platform.match do\n              engine(:ruby)  { Sequel::SQLite::Database }\n              engine(:jruby) { Sequel::JDBC::Database }\n            end\n\n            expect(connection.raw).to be_kind_of(expected)\n          end\n        end\n      end\n\n      with_platform(db: :postgresql) do\n        context \"when postgres\" do\n          it \"returns raw sequel connection\" do\n            expected = Platform.match do\n              engine(:ruby)  { Sequel::Postgres::Database }\n              engine(:jruby) { Sequel::JDBC::Database }\n            end\n\n            expect(connection.raw).to be_kind_of(expected)\n          end\n        end\n      end\n\n      with_platform(db: :mysql) do\n        context \"when mysql\" do\n          it \"returns raw sequel connection\" do\n            expected = Platform.match do\n              engine(:ruby)  { Sequel::Mysql2::Database }\n              engine(:jruby) { Sequel::JDBC::Database }\n            end\n\n            expect(connection.raw).to be_kind_of(expected)\n          end\n        end\n      end\n    end\n\n    # See https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING\n    describe \"when connection components in uri params\" do\n      let(:hanami_model_configuration) do\n        OpenStruct.new(\n          url: \"postgresql:///mydb?host=localhost&port=6433&user=postgres&password=testpasswd\"\n        )\n      end\n\n      it \"returns configured database\" do\n        expect(connection.database).to eq(\"mydb\")\n      end\n\n      it \"returns configured user\" do\n        expect(connection.user).to eq(\"postgres\")\n      end\n\n      it \"returns configured password\" do\n        expect(connection.password).to eq(\"testpasswd\")\n      end\n\n      it \"returns configured host\" do\n        expect(connection.host).to eq(\"localhost\")\n      end\n\n      it \"returns configured port\" do\n        expect(connection.port).to eq(6433)\n      end\n\n      describe \"with blank port\" do\n        let(:hanami_model_configuration) do\n          OpenStruct.new(\n            url: \"postgresql:///mydb?host=localhost&port=&user=postgres&password=testpasswd\"\n          )\n        end\n\n        it \"raises an error\" do\n          expect(connection.port).to be_nil\n        end\n      end\n    end\n  end\n\n  describe \"when jdbc connection\" do\n    let(:hanami_model_configuration) do\n      OpenStruct.new(\n        url: \"jdbc:postgresql://127.0.0.1:5432/database?user=postgres&password=s3cr3T\"\n      )\n    end\n\n    describe \"#jdbc?\" do\n      it \"returns true\" do\n        expect(connection.jdbc?).to eq(true)\n      end\n    end\n\n    describe \"#host\" do\n      it \"returns configured host\" do\n        expect(connection.host).to eq(\"127.0.0.1\")\n      end\n    end\n\n    describe \"#port\" do\n      it \"returns configured port\" do\n        expect(connection.port).to eq(5432)\n      end\n    end\n\n    describe \"#user\" do\n      it \"returns configured user\" do\n        expect(connection.user).to eq(\"postgres\")\n      end\n\n      describe \"when there is no user option\" do\n        let(:hanami_model_configuration) do\n          OpenStruct.new(url: \"jdbc:postgresql://127.0.0.1/database\")\n        end\n\n        it \"returns nil\" do\n          expect(connection.user).to be_nil\n        end\n      end\n    end\n\n    describe \"#password\" do\n      it \"returns configured password\" do\n        expect(connection.password).to eq(\"s3cr3T\")\n      end\n\n      describe \"when there is no password option\" do\n        let(:hanami_model_configuration) do\n          OpenStruct.new(url: \"jdbc:postgresql://127.0.0.1/database\")\n        end\n\n        it \"returns nil\" do\n          expect(connection.password).to be_nil\n        end\n      end\n    end\n\n    describe \"#database\" do\n      it \"returns configured database\" do\n        expect(connection.database).to eq(\"database\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/migrator/mysql.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"ostruct\"\nrequire \"securerandom\"\n\nRSpec.shared_examples \"migrator_mysql\" do\n  let(:migrator) do\n    Hanami::Model::Migrator.new(configuration: configuration)\n  end\n\n  let(:random) { SecureRandom.hex(4) }\n\n  # General variables\n  let(:migrations)     { Pathname.new(__dir__ + \"/../../../../support/fixtures/migrations\") }\n  let(:schema)         { nil }\n  let(:config)         { OpenStruct.new(backend: :sql, url: url, _migrations: migrations, _schema: schema, migrations_logger: Hanami::Model::Migrator::Logger.new(ENV[\"HANAMI_DATABASE_LOGGER\"])) }\n  let(:configuration)  { Hanami::Model::Configuration.new(config) }\n\n  # Variables for `apply` and `prepare`\n  let(:root)              { Pathname.new(\"#{__dir__}/../../../../../tmp\").expand_path }\n  let(:source_migrations) { Pathname.new(\"#{__dir__}/../../../../support/fixtures/migrations\") }\n  let(:target_migrations) { root.join(\"migrations-#{random}\") }\n\n  after do\n    migrator.drop rescue nil # rubocop:disable Style/RescueModifier\n  end\n\n  describe \"MySQL\" do\n    let(:database) { \"mysql_#{random}\" }\n\n    let(:url) do\n      db = database\n      credentials = [\n        ENV[\"HANAMI_DATABASE_USERNAME\"],\n        ENV[\"HANAMI_DATABASE_PASSWORD\"]\n      ].compact.join(\":\")\n\n      Platform.match do\n        engine(:ruby) { \"mysql2://#{credentials}@#{ENV['HANAMI_DATABASE_HOST']}/#{db}?user=#{ENV['HANAMI_DATABASE_USERNAME']}\" }\n        engine(:jruby) { \"jdbc:mysql://localhost/#{db}?user=#{ENV['HANAMI_DATABASE_USERNAME']}&useSSL=false\" }\n      end\n    end\n\n    describe \"create\" do\n      it \"creates the database\" do\n        migrator.create\n\n        connection = Sequel.connect(url)\n        expect(connection.tables).to be_empty\n      end\n\n      it \"raises error when can't connect to database\" do\n        expect(Sequel).to receive(:connect).at_least(:once).and_raise(Sequel::DatabaseError.new(\"ouch\"))\n\n        expect { migrator.create }.to raise_error do |error|\n          expect(error).to be_a(Hanami::Model::MigrationError)\n          expect(error.message).to eq(\"ouch\")\n        end\n      end\n\n      it \"raises error if database is busy\" do\n        migrator.create\n        Sequel.connect(url).tables\n\n        expect { migrator.create }.to raise_error do |error|\n          expect(error).to be_a(Hanami::Model::MigrationError)\n          expect(error.message).to include(\"Database creation failed. If the database exists,\")\n          expect(error.message).to include(\"then its console may be open. See this issue for more details:\")\n          expect(error.message).to include(\"https://github.com/hanami/model/issues/250\")\n        end\n      end\n\n      # See https://github.com/hanami/model/issues/381\n      describe \"when database name contains a dash\" do\n        let(:database) { \"db-name-create_#{random}\" }\n\n        it \"creates the database\" do\n          migrator.create\n\n          connection = Sequel.connect(url)\n          expect(connection.tables).to be_empty\n        end\n      end\n    end\n\n    describe \"drop\" do\n      before do\n        migrator.create\n      end\n\n      it \"drops the database\" do\n        migrator.drop\n        expect { Sequel.connect(url).tables }.to raise_error(Sequel::DatabaseConnectionError)\n      end\n\n      it \"raises error if database doesn't exist\" do\n        migrator.drop # remove the first time\n\n        expect { migrator.drop }\n          .to raise_error(Hanami::Model::MigrationError, \"Cannot find database: #{database}\")\n      end\n\n      it \"raises error when can't connect to database\" do\n        expect(Sequel).to receive(:connect).at_least(:once).and_raise(Sequel::DatabaseError.new(\"ouch\"))\n\n        expect { migrator.drop }.to raise_error do |error|\n          expect(error).to be_a(Hanami::Model::MigrationError)\n          expect(error.message).to eq(\"ouch\")\n        end\n      end\n\n      # See https://github.com/hanami/model/issues/381\n      describe \"when database name contains a dash\" do\n        let(:database) { \"db-name-drop_#{random}\" }\n\n        it \"drops the database\" do\n          migrator.drop\n\n          expect { Sequel.connect(url).tables }.to raise_error(Sequel::DatabaseConnectionError)\n        end\n      end\n    end\n\n    describe \"migrate\" do\n      before do\n        migrator.create\n      end\n\n      describe \"when no migrations\" do\n        let(:migrations) { Pathname.new(__dir__ + \"/../../../../support/fixtures/empty_migrations\") }\n\n        it \"it doesn't alter database\" do\n          migrator.migrate\n\n          connection = Sequel.connect(url)\n          expect(connection.tables).to be_empty\n        end\n      end\n\n      describe \"when migrations are present\" do\n        it \"migrates the database\" do\n          migrator.migrate\n\n          connection = Sequel.connect(url)\n          expect(connection.tables).to_not be_empty\n\n          table = connection.schema(:reviews)\n\n          name, options = table[0] # id\n          expect(name).to eq(:id)\n\n          expect(options.fetch(:allow_null)).to eq(false)\n          expect(options.fetch(:default)).to be_nil\n          expect(options.fetch(:type)).to eq(:integer)\n          expect(options.fetch(:db_type)).to eq(\"int\")\n          expect(options.fetch(:primary_key)).to eq(true)\n          expect(options.fetch(:auto_increment)).to eq(true)\n\n          name, options = table[1] # title\n          expect(name).to eq(:title)\n\n          expect(options.fetch(:allow_null)).to eq(false)\n          expect(options.fetch(:default)).to be_nil\n          expect(options.fetch(:type)).to eq(:string)\n          expect(options.fetch(:db_type)).to eq(\"varchar(255)\")\n          expect(options.fetch(:primary_key)).to eq(false)\n\n          name, options = table[2] # rating (second migration)\n          expect(name).to eq(:rating)\n\n          expect(options.fetch(:allow_null)).to eq(true)\n          expect(options.fetch(:default)).to eq(\"0\")\n          expect(options.fetch(:type)).to eq(:integer)\n          expect(options.fetch(:db_type)).to eq(\"int\")\n          expect(options.fetch(:primary_key)).to eq(false)\n        end\n      end\n\n      describe \"when migrations are ran twice\" do\n        before do\n          migrator.migrate\n        end\n\n        it \"doesn't alter the schema\" do\n          migrator.migrate\n\n          connection = Sequel.connect(url)\n          expect(connection.tables).to_not be_empty\n          expect(connection.tables).to eq(%i[reviews schema_migrations])\n        end\n      end\n\n      describe \"migrate down\" do\n        before do\n          migrator.migrate\n        end\n\n        it \"migrates the database\" do\n          migrator.migrate(version: \"20160831073534\") # see spec/support/fixtures/migrations\n\n          connection = Sequel.connect(url)\n          expect(connection.tables).to_not be_empty\n\n          table = connection.schema(:reviews)\n\n          name, options = table[0] # id\n          expect(name).to eq(:id)\n\n          expect(options.fetch(:allow_null)).to eq(false)\n          expect(options.fetch(:default)).to be_nil\n          expect(options.fetch(:type)).to eq(:integer)\n          expect(options.fetch(:db_type)).to eq(\"int\")\n          expect(options.fetch(:primary_key)).to eq(true)\n          expect(options.fetch(:auto_increment)).to eq(true)\n\n          name, options = table[1] # title\n          expect(name).to eq(:title)\n\n          expect(options.fetch(:allow_null)).to eq(false)\n          expect(options.fetch(:default)).to be_nil\n          expect(options.fetch(:type)).to eq(:string)\n          expect(options.fetch(:db_type)).to eq(\"varchar(255)\")\n          expect(options.fetch(:primary_key)).to eq(false)\n\n          name, options = table[2] # rating (rolled back second migration)\n          expect(name).to be_nil\n          expect(options).to be_nil\n        end\n      end\n    end\n\n    describe \"rollback\" do\n      before do\n        migrator.create\n      end\n\n      describe \"when no migrations\" do\n        let(:migrations) { Pathname.new(__dir__ + \"/../../../../support/fixtures/empty_migrations\") }\n\n        it \"it doesn't alter database\" do\n          migrator.rollback\n\n          connection = Sequel.connect(url)\n          expect(connection.tables).to be_empty\n        end\n      end\n\n      describe \"when migrations are present\" do\n        it \"rollbacks one migration (default)\" do\n          migrator.migrate\n          migrator.rollback\n\n          connection = Sequel.connect(url)\n          expect(connection.tables).to include(:reviews)\n\n          table = connection.schema(:reviews)\n\n          name, options = table[0] # id\n          expect(name).to eq(:id)\n\n          expect(options.fetch(:allow_null)).to eq(false)\n          expect(options.fetch(:default)).to be_nil\n          expect(options.fetch(:type)).to eq(:integer)\n          expect(options.fetch(:db_type)).to eq(\"int\")\n          expect(options.fetch(:primary_key)).to eq(true)\n          expect(options.fetch(:auto_increment)).to eq(true)\n\n          name, options = table[1] # title\n          expect(name).to eq(:title)\n\n          expect(options.fetch(:allow_null)).to eq(false)\n          expect(options.fetch(:default)).to be_nil\n          expect(options.fetch(:type)).to eq(:string)\n          expect(options.fetch(:db_type)).to eq(\"varchar(255)\")\n          expect(options.fetch(:primary_key)).to eq(false)\n\n          name, options = table[2] # rating (second migration)\n          expect(name).to eq(nil)\n          expect(options).to eq(nil)\n        end\n\n        it \"rollbacks several migrations\" do\n          migrator.migrate\n          migrator.rollback(steps: 2)\n\n          connection = Sequel.connect(url)\n          expect(connection.tables).to eq([:schema_migrations])\n        end\n      end\n    end\n\n    describe \"apply\" do\n      let(:migrations) { target_migrations }\n      let(:schema) { root.join(\"schema-#{random}.sql\") }\n\n      before do\n        prepare_migrations_directory\n        migrator.create\n      end\n\n      after do\n        clean_migrations\n      end\n\n      it \"migrates to latest version\" do\n        migrator.apply\n        connection = Sequel.connect(url)\n        migration = connection[:schema_migrations].to_a.last\n\n        expect(migration.fetch(:filename)).to include(\"20160831090612\") # see spec/support/fixtures/migrations\n      end\n\n      it \"dumps database schema.sql\" do\n        migrator.apply\n        actual = schema.read\n\n        expect(actual).to include %(DROP TABLE IF EXISTS `reviews`;)\n\n        expect(actual).to include %(CREATE TABLE `reviews`)\n\n        expect(actual).to include %(`id` int NOT NULL AUTO_INCREMENT,)\n\n        expect(actual).to include %(`title` varchar(255))\n\n        expect(actual).to include %(`rating` int DEFAULT '0',)\n        expect(actual).to include %(PRIMARY KEY \\(`id`\\))\n\n        expect(actual).to include %(DROP TABLE IF EXISTS `schema_migrations`;)\n\n        expect(actual).to include %(CREATE TABLE `schema_migrations` \\()\n\n        expect(actual).to include %(`filename` varchar(255))\n        expect(actual).to include %(PRIMARY KEY (`filename`))\n\n        expect(actual).to include %(LOCK TABLES `schema_migrations` WRITE;)\n\n        # expect(actual).to include %(INSERT INTO `schema_migrations` VALUES \\('20150610133853_create_books.rb'\\),\\('20150610141017_add_price_to_books.rb'\\);)\n\n        expect(actual).to include %(UNLOCK TABLES;)\n      end\n\n      it \"deletes all the migrations\" do\n        migrator.apply\n        expect(target_migrations.children).to be_empty\n      end\n\n      context \"when a system call fails\" do\n        before do\n          expect(migrator).to receive(:adapter).at_least(:once).and_return(adapter)\n        end\n\n        let(:adapter) { Hanami::Model::Migrator::Adapter.for(configuration) }\n\n        it \"raises error when fails to dump database structure\" do\n          expect(adapter).to receive(:dump_structure).and_raise(Hanami::Model::MigrationError, message = \"there was a problem\")\n          expect { migrator.apply }.to raise_error(Hanami::Model::MigrationError, message)\n\n          expect(target_migrations.children).to_not be_empty\n        end\n\n        it \"raises error when fails to dump migrations data\" do\n          expect(adapter).to receive(:dump_migrations_data).and_raise(Hanami::Model::MigrationError, message = \"there was another problem\")\n          expect { migrator.apply }.to raise_error(Hanami::Model::MigrationError, message)\n\n          expect(target_migrations.children).to_not be_empty\n        end\n      end\n    end\n\n    describe \"prepare\" do\n      let(:migrations) { target_migrations }\n      let(:schema)     { root.join(\"schema-#{random}.sql\") }\n\n      before do\n        prepare_migrations_directory\n        migrator.create\n        migrator.migrate\n      end\n\n      after do\n        clean_migrations\n      end\n\n      it \"creates database, loads schema and migrate\" do\n        # Simulate already existing schema.sql, without existing database and pending migrations\n        connection = Sequel.connect(url)\n        Hanami::Model::Migrator::Adapter.for(configuration).dump\n\n        migration = target_migrations.join(\"20160831095616_create_abuses.rb\")\n        File.open(migration, \"w+\") do |f|\n          f.write <<~RUBY\n            Hanami::Model.migration do\n              change do\n                create_table :abuses do\n                  primary_key :id\n                end\n              end\n            end\n          RUBY\n        end\n\n        migrator.prepare\n\n        tables = connection.tables\n        expect(tables).to include(:schema_migrations)\n        expect(tables).to include(:reviews)\n        expect(tables).to include(:abuses)\n\n        FileUtils.rm_f migration\n      end\n\n      it \"works even if schema doesn't exist\" do\n        # Simulate no database, no schema and pending migrations\n        FileUtils.rm_f schema\n\n        migrator.prepare\n\n        connection = Sequel.connect(url)\n        expect(connection.tables).to include(:schema_migrations)\n        expect(connection.tables).to include(:reviews)\n      end\n\n      it \"drops the database and recreates it\" do\n        migrator.prepare\n\n        connection = Sequel.connect(url)\n        expect(connection.tables).to include(:schema_migrations)\n        expect(connection.tables).to include(:reviews)\n      end\n    end\n\n    describe \"version\" do\n      before do\n        migrator.create\n      end\n\n      describe \"when no migrations were ran\" do\n        it \"returns nil\" do\n          expect(migrator.version).to be_nil\n        end\n      end\n\n      describe \"with migrations\" do\n        before do\n          migrator.migrate\n        end\n\n        it \"returns current database version\" do\n          expect(migrator.version).to eq(\"20160831090612\") # see spec/support/fixtures/migrations)\n        end\n      end\n    end\n  end\n\n  private\n\n  def prepare_migrations_directory\n    target_migrations.mkpath\n    FileUtils.cp_r(Dir.glob(\"#{source_migrations}/*.rb\"), target_migrations)\n  end\n\n  def clean_migrations\n    FileUtils.rm_rf(target_migrations)\n    FileUtils.rm(schema) if schema.exist?\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/migrator/postgresql.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"ostruct\"\nrequire \"securerandom\"\n\nRSpec.shared_examples \"migrator_postgresql\" do\n  let(:migrator) do\n    Hanami::Model::Migrator.new(configuration: configuration)\n  end\n\n  let(:random) { SecureRandom.hex(4) }\n\n  # General variables\n  let(:migrations)     { Pathname.new(__dir__ + \"/../../../../support/fixtures/migrations\") }\n  let(:schema)         { nil }\n  let(:config)         { OpenStruct.new(backend: :sql, url: url, _migrations: migrations, _schema: schema, migrations_logger: Hanami::Model::Migrator::Logger.new(ENV[\"HANAMI_DATABASE_LOGGER\"])) }\n  let(:configuration)  { Hanami::Model::Configuration.new(config) }\n\n  # Variables for `apply` and `prepare`\n  let(:root)              { Pathname.new(\"#{__dir__}/../../../../../tmp\").expand_path }\n  let(:source_migrations) { Pathname.new(\"#{__dir__}/../../../../support/fixtures/migrations\") }\n\n  let(:target_migrations) { root.join(\"migrations-#{random}\") }\n\n  after do\n    migrator.drop rescue nil # rubocop:disable Style/RescueModifier\n  end\n\n  describe \"PostgreSQL\" do\n    let(:database) { random }\n    let(:url) do\n      db = database\n      uri = format(\"%<host>s/%<db>s?user=%<user>s&password=%<password>s\",\n                   host: ENV.fetch(\"HANAMI_DATABASE_HOST\", \"127.0.0.1\"),\n                   db: db,\n                   user: ENV[\"HANAMI_DATABASE_USERNAME\"],\n                   password: ENV[\"HANAMI_DATABASE_PASSWORD\"])\n\n      Platform.match do\n        engine(:ruby) { \"postgresql://#{uri}\" }\n        engine(:jruby) { \"jdbc:postgresql://#{uri}\" }\n      end\n    end\n\n    describe \"create\" do\n      before do\n        migrator.create\n      end\n\n      it \"creates the database\" do\n        connection = Sequel.connect(url)\n        expect(connection.tables).to be_empty\n      end\n\n      it \"raises error if database is busy\" do\n        Sequel.connect(url).tables\n        expect { migrator.create }.to raise_error do |error|\n          expect(error).to be_a(Hanami::Model::MigrationError)\n\n          expect(error.message).to include(\"createdb: database creation failed. If the database exists,\")\n          expect(error.message).to include(\"then its console may be open. See this issue for more details:\")\n          expect(error.message).to include(\"https://github.com/hanami/model/issues/250\")\n        end\n      end\n    end\n\n    describe \"drop\" do\n      before do\n        migrator.create\n      end\n\n      it \"drops the database\" do\n        migrator.drop\n\n        expect { Sequel.connect(url).tables }.to raise_error(Sequel::DatabaseConnectionError)\n      end\n\n      it \"raises error if database doesn't exist\" do\n        migrator.drop # remove the first time\n\n        expect { migrator.drop }\n          .to raise_error(Hanami::Model::MigrationError, \"Cannot find database: #{database}\")\n      end\n    end\n\n    describe \"when executables are not available\" do\n      before do\n        # We accomplish having a command not be available by setting PATH\n        # to an empty string, which means *no commands* are available.\n        @original_path = ENV[\"PATH\"]\n        ENV[\"PATH\"] = \"\"\n      end\n\n      after do\n        ENV[\"PATH\"] = @original_path\n      end\n\n      it \"raises MigrationError on missing `createdb`\" do\n        message = Platform.match do\n          os(:macos).engine(:jruby) { \"createdb\" }\n          default { \"Could not find executable in your PATH: `createdb`\" }\n        end\n\n        expect { migrator.create }.to raise_error do |exception|\n          expect(exception).to         be_kind_of(Hanami::Model::MigrationError)\n          expect(exception.message).to include(message)\n        end\n      end\n\n      it \"raises MigrationError on missing `dropdb`\" do\n        message = Platform.match do\n          os(:macos).engine(:jruby) { \"dropdb\" }\n          default { \"Could not find executable in your PATH: `dropdb`\" }\n        end\n\n        expect { migrator.drop }.to raise_error do |exception|\n          expect(exception).to         be_kind_of(Hanami::Model::MigrationError)\n          expect(exception.message).to include(message)\n        end\n      end\n    end\n\n    describe \"migrate\" do\n      before do\n        migrator.create\n      end\n\n      describe \"when no migrations\" do\n        let(:migrations) { Pathname.new(__dir__ + \"/../../../../support/fixtures/empty_migrations\") }\n\n        it \"it doesn't alter database\" do\n          migrator.migrate\n\n          connection = Sequel.connect(url)\n          expect(connection.tables).to be_empty\n        end\n      end\n\n      describe \"when migrations are present\" do\n        it \"migrates the database\" do\n          migrator.migrate\n\n          connection = Sequel.connect(url)\n          expect(connection.tables).to_not be_empty\n\n          table = connection.schema(:reviews)\n\n          name, options = table[0] # id\n          expect(name).to eq(:id)\n\n          expect(options.fetch(:allow_null)).to eq(false)\n          expect(options.fetch(:default)).to eq(\"nextval('reviews_id_seq'::regclass)\")\n          expect(options.fetch(:type)).to eq(:integer)\n          expect(options.fetch(:db_type)).to eq(\"integer\")\n          expect(options.fetch(:primary_key)).to eq(true)\n          expect(options.fetch(:auto_increment)).to eq(true)\n\n          name, options = table[1] # title\n          expect(name).to eq(:title)\n\n          expect(options.fetch(:allow_null)).to eq(false)\n          expect(options.fetch(:default)).to be_nil\n          expect(options.fetch(:type)).to eq(:string)\n          expect(options.fetch(:db_type)).to eq(\"text\")\n          expect(options.fetch(:primary_key)).to eq(false)\n\n          name, options = table[2] # rating (second migration)\n          expect(name).to eq(:rating)\n\n          expect(options.fetch(:allow_null)).to eq(true)\n          expect(options.fetch(:default)).to eq(\"0\")\n          expect(options.fetch(:type)).to eq(:integer)\n          expect(options.fetch(:db_type)).to eq(\"integer\")\n          expect(options.fetch(:primary_key)).to eq(false)\n        end\n      end\n\n      describe \"when migrations are ran twice\" do\n        before do\n          migrator.migrate\n        end\n\n        it \"doesn't alter the schema\" do\n          migrator.migrate\n\n          connection = Sequel.connect(url)\n          expect(connection.tables).to_not be_empty\n          expect(connection.tables).to include(:schema_migrations)\n          expect(connection.tables).to include(:reviews)\n        end\n      end\n\n      describe \"migrate down\" do\n        before do\n          migrator.migrate\n        end\n\n        it \"migrates the database\" do\n          migrator.migrate(version: \"20160831073534\") # see spec/support/fixtures/migrations\n\n          connection = Sequel.connect(url)\n          expect(connection.tables).to_not be_empty\n\n          table = connection.schema(:reviews)\n\n          name, options = table[0] # id\n          expect(name).to eq(:id)\n\n          expect(options.fetch(:allow_null)).to eq(false)\n          expect(options.fetch(:default)).to eq(\"nextval('reviews_id_seq'::regclass)\")\n          expect(options.fetch(:type)).to eq(:integer)\n          expect(options.fetch(:db_type)).to eq(\"integer\")\n          expect(options.fetch(:primary_key)).to eq(true)\n          expect(options.fetch(:auto_increment)).to eq(true)\n\n          name, options = table[1] # title\n          expect(name).to eq(:title)\n\n          expect(options.fetch(:allow_null)).to eq(false)\n          expect(options.fetch(:default)).to be_nil\n          expect(options.fetch(:type)).to eq(:string)\n          expect(options.fetch(:db_type)).to eq(\"text\")\n          expect(options.fetch(:primary_key)).to eq(false)\n\n          name, options = table[2] # rating (rolled back second migration)\n          expect(name).to be_nil\n          expect(options).to be_nil\n        end\n      end\n    end\n\n    describe \"rollback\" do\n      before do\n        migrator.create\n      end\n\n      describe \"when no migrations\" do\n        let(:migrations) { Pathname.new(__dir__ + \"/../../../../support/fixtures/empty_migrations\") }\n\n        it \"it doesn't alter database\" do\n          migrator.rollback\n\n          connection = Sequel.connect(url)\n          expect(connection.tables).to be_empty\n        end\n      end\n\n      describe \"when migrations are present\" do\n        it \"rollbacks one migration (default)\" do\n          migrator.migrate\n          migrator.rollback\n\n          connection = Sequel.connect(url)\n          expect(connection.tables).to include(:reviews)\n\n          table = connection.schema(:reviews)\n\n          name, options = table[0] # id\n          expect(name).to eq(:id)\n\n          expect(options.fetch(:allow_null)).to eq(false)\n          expect(options.fetch(:default)).to eq(\"nextval('reviews_id_seq'::regclass)\")\n          expect(options.fetch(:type)).to eq(:integer)\n          expect(options.fetch(:db_type)).to eq(\"integer\")\n          expect(options.fetch(:primary_key)).to eq(true)\n          expect(options.fetch(:auto_increment)).to eq(true)\n\n          name, options = table[1] # title\n          expect(name).to eq(:title)\n\n          expect(options.fetch(:allow_null)).to eq(false)\n          expect(options.fetch(:default)).to be_nil\n          expect(options.fetch(:type)).to eq(:string)\n          expect(options.fetch(:db_type)).to eq(\"text\")\n          expect(options.fetch(:primary_key)).to eq(false)\n\n          name, options = table[2] # rating (second migration)\n          expect(name).to eq(nil)\n          expect(options).to eq(nil)\n        end\n\n        it \"rollbacks several migrations\" do\n          migrator.migrate\n          migrator.rollback(steps: 2)\n\n          connection = Sequel.connect(url)\n          expect(connection.tables).to eq([:schema_migrations])\n        end\n      end\n    end\n\n    describe \"apply\" do\n      let(:migrations) { target_migrations }\n      let(:schema)     { root.join(\"schema-postgresql-#{random}.sql\") }\n\n      before do\n        prepare_migrations_directory\n        migrator.create\n      end\n\n      after do\n        clean_migrations\n      end\n\n      it \"migrates to latest version\" do\n        migrator.apply\n        connection = Sequel.connect(url)\n        migration = connection[:schema_migrations].to_a.last\n\n        expect(migration.fetch(:filename)).to include(\"20160831090612\") # see spec/support/fixtures/migrations\n      end\n\n      it \"dumps database schema.sql\" do\n        migrator.apply\n        actual = schema.read\n\n        if actual =~ /public\\.reviews/\n          #\n          # POSTGRESQL 10\n          #\n          expect(actual).to include <<~SQL\n            CREATE TABLE public.reviews (\n                id integer NOT NULL,\n                title text NOT NULL,\n                rating integer DEFAULT 0\n            );\n          SQL\n\n          expect(actual).to include <<~SQL\n            CREATE SEQUENCE public.reviews_id_seq\n                AS integer\n                START WITH 1\n                INCREMENT BY 1\n                NO MINVALUE\n                NO MAXVALUE\n                CACHE 1;\n          SQL\n\n          expect(actual).to include <<~SQL\n            ALTER SEQUENCE public.reviews_id_seq OWNED BY public.reviews.id;\n          SQL\n\n          expect(actual).to include <<~SQL\n            ALTER TABLE ONLY public.reviews ALTER COLUMN id SET DEFAULT nextval('public.reviews_id_seq'::regclass);\n          SQL\n\n          expect(actual).to include <<~SQL\n            ALTER TABLE ONLY public.reviews\n                ADD CONSTRAINT reviews_pkey PRIMARY KEY (id);\n          SQL\n\n          expect(actual).to include <<~SQL\n            CREATE TABLE public.schema_migrations (\n                filename text NOT NULL\n            );\n          SQL\n\n          expect(actual).to include <<~SQL\n            COPY public.schema_migrations (filename) FROM stdin;\n            20160831073534_create_reviews.rb\n            20160831090612_add_rating_to_reviews.rb\n          SQL\n\n          expect(actual).to include <<~SQL\n            ALTER TABLE ONLY public.schema_migrations\n                ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (filename);\n          SQL\n        else\n          #\n          # POSTGRESQL 9\n          #\n          expect(actual).to include <<~SQL\n            CREATE TABLE reviews (\n                id integer NOT NULL,\n                title text NOT NULL,\n                rating integer DEFAULT 0\n            );\n          SQL\n\n          expect(actual).to include <<~SQL\n            CREATE SEQUENCE reviews_id_seq\n                START WITH 1\n                INCREMENT BY 1\n                NO MINVALUE\n                NO MAXVALUE\n                CACHE 1;\n          SQL\n\n          expect(actual).to include <<~SQL\n            ALTER SEQUENCE reviews_id_seq OWNED BY reviews.id;\n          SQL\n\n          expect(actual).to include <<~SQL\n            ALTER TABLE ONLY reviews ALTER COLUMN id SET DEFAULT nextval('reviews_id_seq'::regclass);\n          SQL\n\n          expect(actual).to include <<~SQL\n            ALTER TABLE ONLY reviews\n                ADD CONSTRAINT reviews_pkey PRIMARY KEY (id);\n          SQL\n\n          expect(actual).to include <<~SQL\n            CREATE TABLE schema_migrations (\n                filename text NOT NULL\n            );\n          SQL\n\n          expect(actual).to include <<~SQL\n            COPY schema_migrations (filename) FROM stdin;\n            20160831073534_create_reviews.rb\n            20160831090612_add_rating_to_reviews.rb\n          SQL\n\n          expect(actual).to include <<~SQL\n            ALTER TABLE ONLY schema_migrations\n                ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (filename);\n          SQL\n        end\n      end\n\n      it \"deletes all the migrations\" do\n        migrator.apply\n        expect(target_migrations.children).to be_empty\n      end\n\n      context \"when a system call fails\" do\n        before do\n          expect(migrator).to receive(:adapter).at_least(:once).and_return(adapter)\n        end\n\n        let(:adapter) { Hanami::Model::Migrator::Adapter.for(configuration) }\n\n        it \"raises error when fails to dump database structure\" do\n          expect(adapter).to receive(:dump_structure).and_raise(Hanami::Model::MigrationError, message = \"there was a problem\")\n          expect { migrator.apply }.to raise_error(Hanami::Model::MigrationError, message)\n\n          expect(target_migrations.children).to_not be_empty\n        end\n\n        it \"raises error when fails to dump migrations data\" do\n          expect(adapter).to receive(:dump_migrations_data).and_raise(Hanami::Model::MigrationError, message = \"there was another problem\")\n          expect { migrator.apply }.to raise_error(Hanami::Model::MigrationError, message)\n\n          expect(target_migrations.children).to_not be_empty\n        end\n      end\n    end\n\n    describe \"prepare\" do\n      let(:migrations) { target_migrations }\n      let(:schema)     { root.join(\"schema-postgresql-#{random}.sql\") }\n\n      before do\n        prepare_migrations_directory\n        migrator.create\n      end\n\n      after do\n        clean_migrations\n      end\n\n      it \"creates database, loads schema and migrate\" do\n        # Simulate already existing schema.sql, without existing database and pending migrations\n        Hanami::Model::Migrator::Adapter.for(configuration).dump\n\n        migration = target_migrations.join(\"20160831095616_create_abuses.rb\")\n        File.open(migration, \"w+\") do |f|\n          f.write <<-RUBY\n          Hanami::Model.migration do\n            change do\n              create_table :abuses do\n                primary_key :id\n              end\n            end\n          end\n          RUBY\n        end\n\n        migrator.prepare\n\n        connection = Sequel.connect(url)\n        tables = connection.tables\n        expect(tables).to include(:schema_migrations)\n        expect(tables).to include(:reviews)\n        expect(tables).to include(:abuses)\n\n        FileUtils.rm_f migration\n      end\n\n      it \"works even if schema doesn't exist\" do\n        # Simulate no database, no schema and pending migrations\n        FileUtils.rm_f schema\n\n        migrator.prepare\n\n        connection = Sequel.connect(url)\n        expect(connection.tables).to include(:schema_migrations)\n        expect(connection.tables).to include(:reviews)\n      end\n\n      it \"drops the database and recreates it\" do\n        migrator.prepare\n\n        connection = Sequel.connect(url)\n        expect(connection.tables).to include(:schema_migrations)\n        expect(connection.tables).to include(:reviews)\n      end\n    end\n\n    describe \"version\" do\n      before do\n        migrator.create\n      end\n\n      describe \"when no migrations were ran\" do\n        it \"returns nil\" do\n          expect(migrator.version).to be_nil\n        end\n      end\n\n      describe \"with migrations\" do\n        before do\n          migrator.migrate\n        end\n\n        it \"returns current database version\" do\n          expect(migrator.version).to eq(\"20160831090612\") # see spec/support/fixtures/migrations)\n        end\n      end\n    end\n  end\n\n  private\n\n  def prepare_migrations_directory\n    target_migrations.mkpath\n    FileUtils.cp_r(Dir.glob(\"#{source_migrations}/*.rb\"), target_migrations)\n  end\n\n  def clean_migrations\n    FileUtils.rm_rf(target_migrations)\n    FileUtils.rm(schema) if schema.exist?\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/migrator/sqlite.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"ostruct\"\nrequire \"securerandom\"\n\nRSpec.shared_examples \"migrator_sqlite\" do\n  let(:migrator) do\n    Hanami::Model::Migrator.new(configuration: configuration)\n  end\n\n  let(:random) { SecureRandom.hex }\n\n  # General variables\n  let(:migrations)     { Pathname.new(__dir__ + \"/../../../../support/fixtures/migrations\") }\n  let(:schema)         { nil }\n  let(:config)         { OpenStruct.new(backend: :sql, url: url, _migrations: migrations, _schema: schema, migrations_logger: Hanami::Model::Migrator::Logger.new(ENV[\"HANAMI_DATABASE_LOGGER\"])) }\n  let(:configuration)  { Hanami::Model::Configuration.new(config) }\n  let(:url) do\n    db = database\n\n    Platform.match do\n      engine(:ruby)  { \"sqlite://#{db}\" }\n      engine(:jruby) { \"jdbc:sqlite://#{db}\" }\n    end\n  end\n\n  # Variables for `apply` and `prepare`\n  let(:root)              { Pathname.new(\"#{__dir__}/../../../../../tmp\").expand_path }\n  let(:source_migrations) { Pathname.new(\"#{__dir__}/../../../../support/fixtures/migrations\") }\n  let(:target_migrations) { root.join(\"migrations-#{random}\") }\n\n  after do\n    migrator.drop rescue nil # rubocop:disable Style/RescueModifier\n  end\n\n  describe \"SQLite filesystem\" do\n    let(:database) do\n      Pathname.new(\"#{__dir__}/../../../../../tmp/create-#{random}.sqlite3\").expand_path\n    end\n\n    describe \"create\" do\n      it \"creates the database\" do\n        migrator.create\n        expect(File.exist?(database)).to be_truthy, \"Expected database #{database} to exist\"\n      end\n\n      describe \"when it doesn't have write permissions\" do\n        let(:database) { \"/usr/bin/create.sqlite3\" }\n\n        it \"raises an error\" do\n          skip if Platform::Ci.ci?(:circle)\n\n          error = Platform.match do\n            os(:macos).engine(:jruby) { Java::JavaLang::RuntimeException }\n            default { Hanami::Model::MigrationError }\n          end\n\n          message = Platform.match do\n            os(:macos).engine(:jruby) { \"Unhandled IOException: java.io.IOException: unhandled errno: Operation not permitted\" }\n            default { \"Permission denied: /usr/bin/create.sqlite3\" }\n          end\n\n          expect { migrator.create }.to raise_error(error, message)\n        end\n      end\n\n      describe \"when the path is relative\" do\n        let(:database) { \"create.sqlite3\" }\n\n        it \"creates the database\" do\n          migrator.create\n          expect(File.exist?(database)).to be_truthy, \"Expected database #{database} to exist\"\n        end\n      end\n    end\n\n    describe \"drop\" do\n      before do\n        migrator.create\n      end\n\n      it \"drops the database\" do\n        migrator.drop\n        expect(File.exist?(database)).to be_falsey, \"Expected database #{database} to NOT exist\"\n      end\n\n      it \"raises error if database doesn't exist\" do\n        migrator.drop # remove the first time\n\n        expect { migrator.drop }\n          .to raise_error(Hanami::Model::MigrationError, \"Cannot find database: #{database}\")\n      end\n    end\n\n    describe \"migrate\" do\n      before do\n        migrator.create\n      end\n\n      describe \"when no migrations\" do\n        let(:migrations) { Pathname.new(__dir__ + \"/../../../../support/fixtures/empty_migrations\") }\n\n        it \"it doesn't alter database\" do\n          migrator.migrate\n\n          connection = Sequel.connect(url)\n          expect(connection.tables).to be_empty\n        end\n      end\n\n      describe \"when migrations are present\" do\n        it \"migrates the database\" do\n          migrator.migrate\n\n          connection = Sequel.connect(url)\n          expect(connection.tables).to_not be_empty\n\n          table = connection.schema(:reviews)\n\n          name, options = table[0] # id\n          expect(name).to eq(:id)\n\n          expect(options.fetch(:allow_null)).to eq(false)\n          expect(options.fetch(:default)).to be_nil\n          expect(options.fetch(:type)).to eq(:integer)\n          expect(options.fetch(:db_type)).to eq(\"integer\")\n          expect(options.fetch(:primary_key)).to eq(true)\n          expect(options.fetch(:auto_increment)).to eq(true)\n\n          name, options = table[1] # title\n          expect(name).to eq(:title)\n\n          expect(options.fetch(:allow_null)).to eq(false)\n          expect(options.fetch(:default)).to be_nil\n          expect(options.fetch(:type)).to eq(:string)\n          expect(options.fetch(:db_type)).to eq(\"varchar(255)\")\n          expect(options.fetch(:primary_key)).to eq(false)\n\n          name, options = table[2] # rating (second migration)\n          expect(name).to eq(:rating)\n\n          expect(options.fetch(:allow_null)).to eq(true)\n          expect(options.fetch(:default)).to eq(\"0\")\n          expect(options.fetch(:type)).to eq(:integer)\n          expect(options.fetch(:db_type)).to eq(\"integer\")\n          expect(options.fetch(:primary_key)).to eq(false)\n        end\n      end\n\n      describe \"when migrations are ran twice\" do\n        before do\n          migrator.migrate\n        end\n\n        it \"doesn't alter the schema\" do\n          migrator.migrate\n\n          connection = Sequel.connect(url)\n          expect(connection.tables).to_not be_empty\n          expect(connection.tables).to eq(%i[schema_migrations reviews])\n        end\n      end\n\n      describe \"migrate down\" do\n        before do\n          migrator.migrate\n        end\n\n        it \"migrates the database\" do\n          migrator.migrate(version: \"20160831073534\") # see spec/support/fixtures/migrations\n\n          connection = Sequel.connect(url)\n          expect(connection.tables).to_not be_empty\n\n          table = connection.schema(:reviews)\n\n          name, options = table[0] # id\n          expect(name).to eq(:id)\n\n          expect(options.fetch(:allow_null)).to eq(false)\n          expect(options.fetch(:default)).to be_nil\n          expect(options.fetch(:type)).to eq(:integer)\n          expect(options.fetch(:db_type)).to eq(\"integer\")\n          expect(options.fetch(:primary_key)).to eq(true)\n          expect(options.fetch(:auto_increment)).to eq(true)\n\n          name, options = table[1] # title\n          expect(name).to eq(:title)\n\n          expect(options.fetch(:allow_null)).to eq(false)\n          expect(options.fetch(:default)).to be_nil\n          expect(options.fetch(:type)).to eq(:string)\n          expect(options.fetch(:db_type)).to eq(\"varchar(255)\")\n          expect(options.fetch(:primary_key)).to eq(false)\n\n          name, options = table[2] # rating (rolled back second migration)\n          expect(name).to be_nil\n          expect(options).to be_nil\n        end\n      end\n    end\n\n    describe \"rollback\" do\n      before do\n        migrator.create\n      end\n\n      describe \"when no migrations\" do\n        let(:migrations) { Pathname.new(__dir__ + \"/../../../../support/fixtures/empty_migrations\") }\n\n        it \"it doesn't alter database\" do\n          migrator.rollback\n\n          connection = Sequel.connect(url)\n          expect(connection.tables).to be_empty\n        end\n      end\n\n      describe \"when migrations are present\" do\n        it \"rollbacks one migration (default)\" do\n          migrator.migrate\n          migrator.rollback\n\n          connection = Sequel.connect(url)\n          expect(connection.tables).to eq(%i[schema_migrations reviews])\n\n          table = connection.schema(:reviews)\n\n          name, options = table[0] # id\n          expect(name).to eq(:id)\n\n          expect(options.fetch(:allow_null)).to eq(false)\n          expect(options.fetch(:default)).to be_nil\n          expect(options.fetch(:type)).to eq(:integer)\n          expect(options.fetch(:db_type)).to eq(\"integer\")\n          expect(options.fetch(:primary_key)).to eq(true)\n          expect(options.fetch(:auto_increment)).to eq(true)\n\n          name, options = table[1] # title\n          expect(name).to eq(:title)\n\n          expect(options.fetch(:allow_null)).to eq(false)\n          expect(options.fetch(:default)).to be_nil\n          expect(options.fetch(:type)).to eq(:string)\n          expect(options.fetch(:db_type)).to eq(\"varchar(255)\")\n          expect(options.fetch(:primary_key)).to eq(false)\n\n          name, options = table[2] # rating (second migration)\n          expect(name).to eq(nil)\n          expect(options).to eq(nil)\n        end\n\n        it \"rollbacks several migrations\" do\n          migrator.migrate\n          migrator.rollback(steps: 2)\n\n          connection = Sequel.connect(url)\n          expect(connection.tables).to eq([:schema_migrations])\n        end\n      end\n    end\n\n    describe \"apply\" do\n      let(:migrations) { target_migrations }\n      let(:schema)     { root.join(\"schema-sqlite-#{random}.sql\") }\n\n      before do\n        prepare_migrations_directory\n      end\n\n      after do\n        clean_migrations\n      end\n\n      it \"migrates to latest version\" do\n        migrator.apply\n        connection = Sequel.connect(url)\n        migration = connection[:schema_migrations].to_a.last\n\n        expect(migration.fetch(:filename)).to include(\"20160831090612\") # see spec/support/fixtures/migrations\n      end\n\n      it \"dumps database schema.sql\" do\n        migrator.apply\n        actual = schema.read\n\n        expect(actual).to include %(CREATE TABLE `schema_migrations` (`filename` varchar(255) NOT NULL PRIMARY KEY);)\n        expect(actual).to include %(CREATE TABLE `reviews` (`id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, `title` varchar(255) NOT NULL, `rating` integer DEFAULT (0));)\n        expect(actual).to match(/INSERT INTO \"?schema_migrations\"? VALUES\\('20160831073534_create_reviews.rb'\\);/)\n        expect(actual).to match(/INSERT INTO \"?schema_migrations\"? VALUES\\('20160831090612_add_rating_to_reviews.rb'\\);/)\n      end\n\n      it \"deletes all the migrations\" do\n        migrator.apply\n        expect(target_migrations.children).to be_empty\n      end\n\n      context \"when a system call fails\" do\n        before do\n          expect(migrator).to receive(:adapter).at_least(:once).and_return(adapter)\n        end\n\n        let(:adapter) { Hanami::Model::Migrator::Adapter.for(configuration) }\n\n        it \"raises error when fails to dump database structure\" do\n          expect(adapter).to receive(:dump_structure).and_raise(Hanami::Model::MigrationError, message = \"there was a problem\")\n          expect { migrator.apply }.to raise_error(Hanami::Model::MigrationError, message)\n\n          expect(target_migrations.children).to_not be_empty\n        end\n\n        it \"raises error when fails to dump migrations data\" do\n          expect(adapter).to receive(:dump_migrations_data).and_raise(Hanami::Model::MigrationError, message = \"there was another problem\")\n          expect { migrator.apply }.to raise_error(Hanami::Model::MigrationError, message)\n\n          expect(target_migrations.children).to_not be_empty\n        end\n\n        it \"raises error when fails to write migrations data\" do\n          expect(File).to receive(:open).and_raise(StandardError, message = \"a standard error\")\n          expect { migrator.apply }.to raise_error(Hanami::Model::MigrationError, message)\n\n          expect(target_migrations.children).to_not be_empty\n        end\n      end\n    end\n\n    describe \"prepare\" do\n      let(:migrations) { target_migrations }\n      let(:schema)     { root.join(\"schema-sqlite-#{random}.sql\") }\n\n      before do\n        prepare_migrations_directory\n      end\n\n      after do\n        clean_migrations\n      end\n\n      it \"creates database, loads schema and migrate\" do\n        # Simulate already existing schema.sql, without existing database and pending migrations\n        connection = Sequel.connect(url)\n        Hanami::Model::Migrator::Adapter.for(configuration).dump\n\n        migration = target_migrations.join(\"20160831095616_create_abuses.rb\")\n        File.open(migration, \"w+\") do |f|\n          f.write <<~RUBY\n            Hanami::Model.migration do\n              change do\n                create_table :abuses do\n                  primary_key :id\n                end\n              end\n            end\n          RUBY\n        end\n\n        migrator.prepare\n\n        expect(connection.tables).to eq(%i[schema_migrations reviews abuses])\n\n        FileUtils.rm_f migration\n      end\n\n      it \"works even if schema doesn't exist\" do\n        # Simulate no database, no schema and pending migrations\n        FileUtils.rm_f schema\n        migrator.prepare\n\n        connection = Sequel.connect(url)\n        expect(connection.tables).to eq(%i[schema_migrations reviews])\n      end\n\n      it \"drops the database and recreate it\" do\n        migrator.create\n        migrator.prepare\n\n        connection = Sequel.connect(url)\n        expect(connection.tables).to include(:schema_migrations)\n        expect(connection.tables).to include(:reviews)\n      end\n    end\n\n    describe \"version\" do\n      before do\n        migrator.create\n      end\n\n      describe \"when no migrations were ran\" do\n        it \"returns nil\" do\n          expect(migrator.version).to be_nil\n        end\n      end\n\n      describe \"with migrations\" do\n        before do\n          migrator.migrate\n        end\n\n        it \"returns current database version\" do\n          expect(migrator.version).to eq(\"20160831090612\") # see spec/support/fixtures/migrations)\n        end\n      end\n    end\n  end\n\n  private\n\n  def prepare_migrations_directory\n    target_migrations.mkpath\n    FileUtils.cp_r(Dir.glob(\"#{source_migrations}/*.rb\"), target_migrations)\n  end\n\n  def clean_migrations\n    FileUtils.rm_rf(target_migrations)\n    FileUtils.rm(schema) if schema.exist?\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/migrator_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"hanami/model/migrator\"\nrequire_relative \"./migrator/#{Database.engine}\"\n\nRSpec.describe Hanami::Model::Migrator do\n  include_examples \"migrator_#{Database.engine}\"\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/not_null_constraint_violation_error_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::NotNullConstraintViolationError do\n  it \"inherits from Hanami::Model::ConstraintViolationError\" do\n    expect(described_class.ancestors).to include(Hanami::Model::ConstraintViolationError)\n  end\n\n  it \"has a default error message\" do\n    expect { raise described_class }.to raise_error(described_class, \"NOT NULL constraint has been violated\")\n  end\n\n  it \"allows custom error message\" do\n    expect { raise described_class.new(\"Ouch\") }.to raise_error(described_class, \"Ouch\")\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/sql/console/mysql.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"hanami/model/sql/consoles/mysql\"\n\nRSpec.shared_examples \"sql_console_mysql\" do\n  let(:sql_console) { Hanami::Model::Sql::Consoles::Mysql.new(uri) }\n\n  describe \"#connection_string\" do\n    let(:uri) { URI.parse(\"mysql://username:password@localhost:1234/foo_development\") }\n\n    it \"returns a connection string\" do\n      expect(sql_console.connection_string).to eq(\"mysql -h localhost -D foo_development -P 1234 -u username -p password\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/sql/console/postgresql.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"hanami/model/sql/consoles/postgresql\"\n\nRSpec.shared_examples \"sql_console_postgresql\" do\n  let(:sql_console) { Hanami::Model::Sql::Consoles::Postgresql.new(uri) }\n\n  around(:each) do |example|\n    original_pgpassword = ENV[\"PGPASSWORD\"]\n    example.run\n    ENV[\"PGPASSWORD\"] = original_pgpassword\n  end\n\n  describe \"#connection_string\" do\n    let(:uri) { URI.parse(\"postgres://username:password@localhost:1234/foo_development\") }\n\n    it \"returns a connection string\" do\n      expect(sql_console.connection_string).to eq(\"psql -h localhost -d foo_development -p 1234 -U username\")\n    end\n\n    it \"sets the PGPASSWORD environment variable\" do\n      sql_console.connection_string\n      expect(ENV[\"PGPASSWORD\"]).to eq(\"password\")\n      ENV.delete(\"PGPASSWORD\")\n    end\n\n    context \"when the password contains percent encoded characters\" do\n      let(:uri) { URI.parse(\"postgres://username:p%40ss@localhost:1234/foo_development\") }\n\n      it \"sets the PGPASSWORD environment variable decoding special characters\" do\n        sql_console.connection_string\n        expect(ENV[\"PGPASSWORD\"]).to eq(\"p@ss\")\n        ENV.delete(\"PGPASSWORD\")\n      end\n    end\n\n    context \"when components of the  hierarchical part of the URI can also be given as parameters\" do\n      let(:uri) { URI.parse(\"postgres:///foo_development?user=username&password=password&host=localhost&port=1234\") }\n\n      it \"returns a connection string\" do\n        expect(sql_console.connection_string).to eq(\"psql -h localhost -d foo_development -p 1234 -U username\")\n      end\n\n      it \"sets the PGPASSWORD environment variable\" do\n        sql_console.connection_string\n        expect(ENV[\"PGPASSWORD\"]).to eq(\"password\")\n        ENV.delete(\"PGPASSWORD\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/sql/console/sqlite.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"hanami/model/sql/consoles/sqlite\"\n\nRSpec.shared_examples \"sql_console_sqlite\" do\n  let(:sql_console) { Hanami::Model::Sql::Consoles::Sqlite.new(uri) }\n\n  describe \"#connection_string\" do\n    describe \"with shell ok database uri\" do\n      let(:uri) { URI.parse(\"sqlite://foo/bar.db\") }\n      it \"returns a connection string for Sqlite3\" do\n        expect(sql_console.connection_string).to eq(\"sqlite3 foo/bar.db\")\n      end\n    end\n\n    describe \"with non shell ok database uri\" do\n      let(:uri) { URI.parse(\"sqlite://foo/%20bar.db\") }\n      it \"returns an escaped connection string for Sqlite3\" do\n        expect(sql_console.connection_string).to eq('sqlite3 foo/\\\\%20bar.db')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/sql/console_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"hanami/model/sql/console\"\n\nRSpec.describe Hanami::Model::Sql::Console do\n  describe \"deciding on which SQL console class to use, based on URI scheme\" do\n    let(:uri) { \"username:password@localhost:1234/foo_development\" }\n\n    case Database.engine\n    when :sqlite\n      it \"sqlite:// uri returns an instance of Console::Sqlite\" do\n        console = Hanami::Model::Sql::Console.new(\"sqlite://#{uri}\").send(:console)\n        expect(console).to be_a_kind_of(Hanami::Model::Sql::Consoles::Sqlite)\n      end\n    when :postgresql\n      it \"postgres:// uri returns an instance of Console::Postgresql\" do\n        console = Hanami::Model::Sql::Console.new(\"postgres://#{uri}\").send(:console)\n        expect(console).to be_a_kind_of(Hanami::Model::Sql::Consoles::Postgresql)\n      end\n\n      it \"postgresql:// uri returns an instance of Console::Postgresql\" do\n        console = Hanami::Model::Sql::Console.new(\"postgresql://#{uri}\").send(:console)\n        expect(console).to be_a_kind_of(Hanami::Model::Sql::Consoles::Postgresql)\n      end\n    when :mysql\n      it \"mysql:// uri returns an instance of Console::Mysql\" do\n        console = Hanami::Model::Sql::Console.new(\"mysql://#{uri}\").send(:console)\n        expect(console).to be_a_kind_of(Hanami::Model::Sql::Consoles::Mysql)\n      end\n\n      it \"mysql2:// uri returns an instance of Console::Mysql\" do\n        console = Hanami::Model::Sql::Console.new(\"mysql2://#{uri}\").send(:console)\n        expect(console).to be_a_kind_of(Hanami::Model::Sql::Consoles::Mysql)\n      end\n    end\n  end\n\n  describe Database.engine.to_s do\n    require_relative \"./console/#{Database.engine}\"\n    include_examples \"sql_console_#{Database.engine}\"\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/sql/entity/schema/automatic_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::Sql::Entity::Schema do\n  describe \"automatic\" do\n    subject { Author.schema }\n\n    describe \"#initialize\" do\n      it \"returns frozen instance\" do\n        expect(subject).to be_frozen\n      end\n    end\n\n    describe \"#call\" do\n      it \"returns empty hash when nil is given\" do\n        result = subject.call(nil)\n\n        expect(result).to eq({})\n      end\n\n      it \"processes attributes\" do\n        now = Time.now\n        result = subject.call(id: 1, created_at: now.to_s)\n\n        expect(result.fetch(:id)).to eq(1)\n        expect(result.fetch(:created_at)).to be_within(2).of(now)\n      end\n\n      it \"ignores unknown attributes\" do\n        result = subject.call(foo: \"bar\")\n\n        expect(result).to eq({})\n      end\n    end\n\n    describe \"#attribute?\" do\n      it \"returns true for known attributes\" do\n        expect(subject.attribute?(:id)).to eq(true)\n      end\n\n      it \"returns false for unknown attributes\" do\n        expect(subject.attribute?(:foo)).to eq(false)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/sql/entity/schema/mapping_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::Sql::Entity::Schema do\n  describe \"mapping\" do\n    subject { Operator.schema }\n\n    describe \"#initialize\" do\n      it \"returns frozen instance\" do\n        expect(subject).to be_frozen\n      end\n    end\n\n    describe \"#call\" do\n      it \"returns empty hash when nil is given\" do\n        result = subject.call(nil)\n\n        expect(result).to eq({})\n      end\n\n      it \"processes attributes\" do\n        result = subject.call(id: 1, name: :foo)\n\n        expect(result).to eq(id: 1, name: \"foo\")\n      end\n\n      it \"ignores unknown attributes\" do\n        result = subject.call(foo: \"bar\")\n\n        expect(result).to eq({})\n      end\n    end\n\n    describe \"#attribute?\" do\n      it \"returns true for known attributes\" do\n        expect(subject.attribute?(:id)).to eq(true)\n      end\n\n      it \"returns false for unknown attributes\" do\n        expect(subject.attribute?(:foo)).to eq(false)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/sql/schema/array_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model::Sql::Types::Schema::Array\" do\n  let(:described_class) { Hanami::Model::Sql::Types::Schema::Array }\n\n  let(:input) do\n    Class.new do\n      def to_ary\n        []\n      end\n    end.new\n  end\n\n  it \"returns nil for nil\" do\n    input = nil\n    expect(described_class[input]).to eq(input)\n  end\n\n  it \"coerces object that respond to #to_ary\" do\n    expect(described_class[input]).to eq(input.to_ary)\n  end\n\n  it \"coerces string\" do\n    input = \"foo\"\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Array(): #{input.inspect}\")\n  end\n\n  it \"raises error for symbol\" do\n    input = :foo\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Array(): #{input.inspect}\")\n  end\n\n  it \"raises error for integer\" do\n    input = 11\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Array(): #{input.inspect}\")\n  end\n\n  it \"raises error for float\" do\n    input = 3.14\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Array(): #{input.inspect}\")\n  end\n\n  it \"raises error for bigdecimal\" do\n    input = BigDecimal(3.14, 10)\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Array(): #{input.inspect}\")\n  end\n\n  it \"raises error for date\" do\n    input = Date.today\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Array(): #{input.inspect}\")\n  end\n\n  it \"raises error for datetime\" do\n    input = DateTime.new\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Array(): #{input.inspect}\")\n  end\n\n  it \"raises error for time\" do\n    input = Time.now\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Array(): #{input.inspect}\")\n  end\n\n  it \"coerces array\" do\n    input = []\n    expect(described_class[input]).to eq(input)\n  end\n\n  it \"raises error for hash\" do\n    input = {}\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Array(): #{input.inspect}\")\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/sql/schema/bool_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model::Sql::Types::Schema::Bool\" do\n  let(:described_class) { Hanami::Model::Sql::Types::Schema::Bool }\n\n  it \"returns nil for nil\" do\n    input = nil\n    expect(described_class[input]).to eq(input)\n  end\n\n  it \"returns true for true\" do\n    input = true\n    expect(described_class[input]).to eq(input)\n  end\n\n  it \"returns false for false\" do\n    input = true\n    expect(described_class[input]).to eq(input)\n  end\n\n  it \"raises error for string\" do\n    input = \"foo\"\n    expect { described_class[input] }\n      .to raise_error(TypeError, \"#{input.inspect} violates constraints (type?(FalseClass, #{input.inspect}) failed)\")\n  end\n\n  it \"raises error for symbol\" do\n    input = :foo\n    expect { described_class[input] }\n      .to raise_error(TypeError, \"#{input.inspect} violates constraints (type?(FalseClass, #{input.inspect}) failed)\")\n  end\n\n  it \"raises error for integer\" do\n    input = 11\n    expect { described_class[input] }\n      .to raise_error(TypeError, \"#{input.inspect} violates constraints (type?(FalseClass, #{input.inspect}) failed)\")\n  end\n\n  it \"raises error for float\" do\n    input = 3.14\n    expect { described_class[input] }\n      .to raise_error(TypeError, \"#{input.inspect} violates constraints (type?(FalseClass, #{input.inspect}) failed)\")\n  end\n\n  it \"raises error for bigdecimal\" do\n    input = BigDecimal(3.14, 10)\n    expect { described_class[input] }\n      .to raise_error(TypeError, \"#{input.inspect} violates constraints (type?(FalseClass, #{input.inspect}) failed)\")\n  end\n\n  it \"raises error for date\" do\n    input = Date.today\n    expect { described_class[input] }\n      .to raise_error(TypeError, \"#{input.inspect} violates constraints (type?(FalseClass, #{input.inspect}) failed)\")\n  end\n\n  it \"raises error for datetime\" do\n    input = DateTime.new\n    expect { described_class[input] }\n      .to raise_error(TypeError, \"#{input.inspect} violates constraints (type?(FalseClass, #{input.inspect}) failed)\")\n  end\n\n  it \"raises error for time\" do\n    input = Time.now\n    expect { described_class[input] }\n      .to raise_error(TypeError, \"#{input.inspect} violates constraints (type?(FalseClass, #{input.inspect}) failed)\")\n  end\n\n  it \"raises error for array\" do\n    input = []\n    expect { described_class[input] }\n      .to raise_error(TypeError, \"#{input.inspect} violates constraints (type?(FalseClass, #{input.inspect}) failed)\")\n  end\n\n  it \"raises error for hash\" do\n    input = {}\n    expect { described_class[input] }\n      .to raise_error(TypeError, \"#{input.inspect} violates constraints (type?(FalseClass, #{input.inspect}) failed)\")\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/sql/schema/date_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model::Sql::Types::Schema::Date\" do\n  let(:described_class) { Hanami::Model::Sql::Types::Schema::Date }\n\n  let(:input) do\n    Class.new do\n      def to_date\n        Date.today\n      end\n    end.new\n  end\n\n  it \"returns nil for nil\" do\n    input = nil\n    expect(described_class[input]).to eq(input)\n  end\n\n  it \"coerces object that respond to #to_date\" do\n    expect(described_class[input]).to eq(input.to_date)\n  end\n\n  it \"coerces string\" do\n    date = Date.today\n    input = date.to_s\n\n    expect(described_class[input]).to eq(date)\n  end\n\n  it \"coerces Hanami string\" do\n    input = Hanami::Utils::String.new(Date.today)\n    expect(described_class[input]).to eq(Date.parse(input))\n  end\n\n  it \"raises error for meaningless string\" do\n    input = \"foo\"\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid date\")\n  end\n\n  it \"raises error for symbol\" do\n    input = :foo\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Date(): #{input.inspect}\")\n  end\n\n  it \"raises error for integer\" do\n    input = 11\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Date(): #{input.inspect}\")\n  end\n\n  it \"raises error for float\" do\n    input = 3.14\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Date(): #{input.inspect}\")\n  end\n\n  it \"raises error for bigdecimal\" do\n    input = BigDecimal(3.14, 10)\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Date(): #{input.inspect}\")\n  end\n\n  it \"coerces date\" do\n    input = Date.today\n    date = input\n\n    expect(described_class[input]).to eq(date)\n  end\n\n  it \"coerces datetime\" do\n    input = DateTime.new\n    date = input.to_date\n\n    expect(described_class[input]).to eq(date)\n  end\n\n  it \"coerces time\" do\n    input = Time.now\n    date = input.to_date\n\n    expect(described_class[input]).to eq(date)\n  end\n\n  it \"raises error for array\" do\n    input = []\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Date(): #{input.inspect}\")\n  end\n\n  it \"raises error for hash\" do\n    input = {}\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Date(): #{input.inspect}\")\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/sql/schema/date_time_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model::Sql::Types::Schema::DateTime\" do\n  let(:described_class) { Hanami::Model::Sql::Types::Schema::DateTime }\n\n  let(:input) do\n    Class.new do\n      def to_datetime\n        DateTime.new\n      end\n    end.new\n  end\n\n  it \"returns nil for nil\" do\n    input = nil\n    expect(described_class[input]).to eq(input)\n  end\n\n  it \"coerces object that respond to #to_datetime\" do\n    expect(described_class[input]).to eq(input.to_datetime)\n  end\n\n  it \"coerces string\" do\n    date = DateTime.new\n    input = date.to_s\n\n    expect(described_class[input]).to eq(date)\n  end\n\n  it \"coerces Hanami string\" do\n    input = Hanami::Utils::String.new(DateTime.new)\n    expect(described_class[input]).to eq(DateTime.parse(input))\n  end\n\n  it \"raises error for meaningless string\" do\n    input = \"foo\"\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid date\")\n  end\n\n  it \"raises error for symbol\" do\n    input = :foo\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for DateTime(): #{input.inspect}\")\n  end\n\n  it \"raises error for integer\" do\n    input = 11\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for DateTime(): #{input.inspect}\")\n  end\n\n  it \"raises error for float\" do\n    input = 3.14\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for DateTime(): #{input.inspect}\")\n  end\n\n  it \"raises error for bigdecimal\" do\n    input = BigDecimal(3.14, 10)\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for DateTime(): #{input.inspect}\")\n  end\n\n  it \"coerces date\" do\n    input = Date.today\n    date_time = input.to_datetime\n\n    expect(described_class[input]).to eq(date_time)\n  end\n\n  it \"coerces datetime\" do\n    input = DateTime.new\n    date_time = input\n\n    expect(described_class[input]).to eq(date_time)\n  end\n\n  it \"coerces time\" do\n    input = Time.now\n    date_time = input.to_datetime\n\n    expect(described_class[input]).to be_within(2).of(date_time)\n  end\n\n  it \"raises error for array\" do\n    input = []\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for DateTime(): #{input.inspect}\")\n  end\n\n  it \"raises error for hash\" do\n    input = {}\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for DateTime(): #{input.inspect}\")\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/sql/schema/decimal_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model::Sql::Types::Schema::Decimal\" do\n  let(:described_class) { Hanami::Model::Sql::Types::Schema::Decimal }\n\n  let(:input) do\n    Class.new do\n      def to_d\n        BigDecimal(10)\n      end\n    end.new\n  end\n\n  it \"returns nil for nil\" do\n    input = nil\n    expect(described_class[input]).to eq(input)\n  end\n\n  it \"coerces object that respond to #to_d\" do\n    expect(described_class[input]).to eq(input.to_d)\n  end\n\n  it \"coerces string representing int\" do\n    input = \"1\"\n    expect(described_class[input]).to eq(input.to_d)\n  end\n\n  it \"coerces Hanami string representing int\" do\n    input = Hanami::Utils::String.new(\"1\")\n    expect(described_class[input]).to eq(input.to_d)\n  end\n\n  it \"coerces string representing float\" do\n    input = \"3.14\"\n    expect(described_class[input]).to eq(input.to_d)\n  end\n\n  it \"coerces Hanami string representing float\" do\n    input = Hanami::Utils::String.new(\"3.14\")\n    expect(described_class[input]).to eq(input.to_d)\n  end\n\n  it \"raises error for symbol\" do\n    input = :house_11\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for BigDecimal(): #{input.inspect}\")\n  end\n\n  it \"coerces integer\" do\n    input = 23\n    expect(described_class[input]).to eq(input.to_d)\n  end\n\n  it \"coerces float\" do\n    input = 3.14\n    expect(described_class[input]).to eq(input.to_d)\n  end\n\n  it \"coerces bigdecimal\" do\n    input = BigDecimal(3.14, 10)\n    expect(described_class[input]).to eq(input.to_d)\n  end\n\n  it \"raises error for date\" do\n    input = Date.today\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for BigDecimal(): #{input.inspect}\")\n  end\n\n  it \"raises error for datetime\" do\n    input = DateTime.new\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for BigDecimal(): #{input.inspect}\")\n  end\n\n  it \"raises error for time\" do\n    input = Time.now\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for BigDecimal(): #{input.inspect}\")\n  end\n\n  it \"raises error for array\" do\n    input = []\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for BigDecimal(): #{input.inspect}\")\n  end\n\n  it \"raises error for hash\" do\n    input = {}\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for BigDecimal(): #{input.inspect}\")\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/sql/schema/float_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model::Sql::Types::Schema::Float\" do\n  let(:described_class) { Hanami::Model::Sql::Types::Schema::Float }\n\n  let(:input) do\n    Class.new do\n      def to_f\n        3.14\n      end\n    end.new\n  end\n\n  it \"returns nil for nil\" do\n    input = nil\n    expect(described_class[input]).to eq(input)\n  end\n\n  it \"coerces object that respond to #to_f\" do\n    expect(described_class[input]).to eq(input.to_f)\n  end\n\n  it \"coerces string representing int\" do\n    input = \"1\"\n    expect(described_class[input]).to eq(input.to_f)\n  end\n\n  it \"coerces Hanami string representing int\" do\n    input = Hanami::Utils::String.new(\"1\")\n    expect(described_class[input]).to eq(input.to_f)\n  end\n\n  it \"coerces string representing float\" do\n    input = \"3.14\"\n    expect(described_class[input]).to eq(input.to_f)\n  end\n\n  it \"coerces Hanami string representing float\" do\n    input = Hanami::Utils::String.new(\"3.14\")\n    expect(described_class[input]).to eq(input.to_f)\n  end\n\n  it \"raises error for meaningless string\" do\n    input = \"foo\"\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Float(): #{input.inspect}\")\n  end\n\n  it \"raises error for symbol\" do\n    input = :house_11\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Float(): #{input.inspect}\")\n  end\n\n  it \"coerces integer\" do\n    input = 23\n    expect(described_class[input]).to eq(input.to_f)\n  end\n\n  it \"coerces float\" do\n    input = 3.14\n    expect(described_class[input]).to eq(input.to_f)\n  end\n\n  it \"coerces bigdecimal\" do\n    input = BigDecimal(3.14, 10)\n    expect(described_class[input]).to eq(input.to_f)\n  end\n\n  it \"raises error for date\" do\n    input = Date.today\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Float(): #{input.inspect}\")\n  end\n\n  it \"raises error for datetime\" do\n    input = DateTime.new\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Float(): #{input.inspect}\")\n  end\n\n  it \"raises error for time\" do\n    input = Time.now\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Float(): #{input.inspect}\")\n  end\n\n  it \"raises error for array\" do\n    input = []\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Float(): #{input.inspect}\")\n  end\n\n  it \"raises error for hash\" do\n    input = {}\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Float(): #{input.inspect}\")\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/sql/schema/hash_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model::Sql::Types::Schema::Hash\" do\n  let(:described_class) { Hanami::Model::Sql::Types::Schema::Hash }\n\n  let(:input) do\n    Class.new do\n      def to_hash\n        Hash[]\n      end\n    end.new\n  end\n\n  it \"returns nil for nil\" do\n    input = nil\n    expect(described_class[input]).to eq(input)\n  end\n\n  it \"coerces object that respond to #to_hash\" do\n    expect(described_class[input]).to eq(input.to_hash)\n  end\n\n  it \"coerces string\" do\n    input = \"foo\"\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Hash(): #{input.inspect}\")\n  end\n\n  it \"raises error for symbol\" do\n    input = :foo\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Hash(): #{input.inspect}\")\n  end\n\n  it \"raises error for integer\" do\n    input = 11\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Hash(): #{input.inspect}\")\n  end\n\n  it \"raises error for float\" do\n    input = 3.14\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Hash(): #{input.inspect}\")\n  end\n\n  it \"raises error for bigdecimal\" do\n    input = BigDecimal(3.14, 10)\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Hash(): #{input.inspect}\")\n  end\n\n  it \"raises error for date\" do\n    input = Date.today\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Hash(): #{input.inspect}\")\n  end\n\n  it \"raises error for datetime\" do\n    input = DateTime.new\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Hash(): #{input.inspect}\")\n  end\n\n  it \"raises error for time\" do\n    input = Time.now\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Hash(): #{input.inspect}\")\n  end\n\n  it \"raises error for array\" do\n    input = []\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Hash(): #{input.inspect}\")\n  end\n\n  it \"coerces hash\" do\n    input = {}\n    expect(described_class[input]).to eq(input)\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/sql/schema/int_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model::Sql::Types::Schema::Int\" do\n  let(:described_class) { Hanami::Model::Sql::Types::Schema::Int }\n\n  let(:input) do\n    Class.new do\n      def to_int\n        23\n      end\n    end.new\n  end\n\n  it \"returns nil for nil\" do\n    input = nil\n    expect(described_class[input]).to eq(input)\n  end\n\n  it \"coerces object that respond to #to_int\" do\n    expect(described_class[input]).to eq(input.to_int)\n  end\n\n  it \"coerces string representing int\" do\n    input = \"1\"\n    expect(described_class[input]).to eq(input.to_i)\n  end\n\n  it \"coerces Hanami string representing int\" do\n    input = Hanami::Utils::String.new(\"1\")\n    expect(described_class[input]).to eq(input.to_i)\n  end\n\n  it \"raises error for meaningless string\" do\n    input = \"foo\"\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Integer(): #{input.inspect}\")\n  end\n\n  it \"raises error for symbol\" do\n    input = :house_11\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Integer(): #{input.inspect}\")\n  end\n\n  it \"coerces integer\" do\n    input = 23\n    expect(described_class[input]).to eq(input)\n  end\n\n  it \"coerces float\" do\n    input = 3.14\n    expect(described_class[input]).to eq(input.to_i)\n  end\n\n  it \"coerces bigdecimal\" do\n    input = BigDecimal(3.14, 10)\n    expect(described_class[input]).to eq(input.to_i)\n  end\n\n  it \"raises error for date\" do\n    input = Date.today\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Integer(): #{input.inspect}\")\n  end\n\n  it \"raises error for datetime\" do\n    input = DateTime.new\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Integer(): #{input.inspect}\")\n  end\n\n  it \"raises error for time\" do\n    input = Time.now\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Integer(): #{input.inspect}\")\n  end\n\n  it \"raises error for array\" do\n    input = []\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Integer(): #{input.inspect}\")\n  end\n\n  it \"raises error for hash\" do\n    input = {}\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Integer(): #{input.inspect}\")\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/sql/schema/string_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model::Sql::Types::Schema::String\" do\n  let(:described_class) { Hanami::Model::Sql::Types::Schema::String }\n\n  it \"returns nil for nil\" do\n    input = nil\n    expect(described_class[input]).to eq(input)\n  end\n\n  it \"coerces string\" do\n    input = \"foo\"\n    expect(described_class[input]).to eq(input.to_s)\n  end\n\n  it \"coerces symbol\" do\n    input = :foo\n    expect(described_class[input]).to eq(input.to_s)\n  end\n\n  it \"coerces integer\" do\n    input = 23\n    expect(described_class[input]).to eq(input.to_s)\n  end\n\n  it \"coerces float\" do\n    input = 3.14\n    expect(described_class[input]).to eq(input.to_s)\n  end\n\n  it \"coerces bigdecimal\" do\n    input = BigDecimal(3.14, 10)\n    expect(described_class[input]).to eq(input.to_s)\n  end\n\n  it \"coerces date\" do\n    input = Date.today\n    expect(described_class[input]).to eq(input.to_s)\n  end\n\n  it \"coerces datetime\" do\n    input = DateTime.new\n    expect(described_class[input]).to eq(input.to_s)\n  end\n\n  it \"coerces time\" do\n    input = Time.now\n    expect(described_class[input]).to eq(input.to_s)\n  end\n\n  it \"coerces array\" do\n    input = []\n    expect(described_class[input]).to eq(input.to_s)\n  end\n\n  it \"coerces hash\" do\n    input = {}\n    expect(described_class[input]).to eq(input.to_s)\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/sql/schema/time_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model::Sql::Types::Schema::Time\" do\n  let(:described_class) { Hanami::Model::Sql::Types::Schema::Time }\n\n  let(:input) do\n    Class.new do\n      def to_time\n        Time.now\n      end\n    end.new\n  end\n\n  it \"returns nil for nil\" do\n    input = nil\n    expect(described_class[input]).to eq(input)\n  end\n\n  it \"coerces object that respond to #to_time\" do\n    expect(described_class[input]).to be_within(2).of(input.to_time)\n  end\n\n  it \"coerces string\" do\n    time = Time.now\n    input = time.to_s\n\n    expect(described_class[input]).to be_within(2).of(time)\n  end\n\n  it \"coerces Hanami string\" do\n    input = Hanami::Utils::String.new(Time.now)\n    expect(described_class[input]).to be_within(2).of(Time.parse(input))\n  end\n\n  it \"raises error for meaningless string\" do\n    input = \"foo\"\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"no time information in #{input.inspect}\")\n  end\n\n  it \"raises error for symbol\" do\n    input = :foo\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Time(): #{input.inspect}\")\n  end\n\n  it \"coerces integer\" do\n    input = 11\n    time = Time.at(input)\n\n    expect(described_class[input]).to be_within(2).of(time)\n  end\n\n  it \"raises error for float\" do\n    input = 3.14\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Time(): #{input.inspect}\")\n  end\n\n  it \"raises error for bigdecimal\" do\n    input = BigDecimal(3.14, 10)\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Time(): #{input.inspect}\")\n  end\n\n  it \"coerces date\" do\n    input = Date.today\n    time = input.to_time\n\n    expect(described_class[input]).to be_within(2).of(time)\n  end\n\n  it \"coerces datetime\" do\n    input = DateTime.new\n    time = input.to_time\n\n    expect(described_class[input]).to be_within(2).of(time)\n  end\n\n  it \"coerces time\" do\n    input = Time.now\n    time = input\n\n    expect(described_class[input]).to be_within(2).of(time)\n  end\n\n  it \"raises error for array\" do\n    input = []\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Time(): #{input.inspect}\")\n  end\n\n  it \"raises error for hash\" do\n    input = {}\n    expect { described_class[input] }\n      .to raise_error(ArgumentError, \"invalid value for Time(): #{input.inspect}\")\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/sql_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::Sql do\n  describe \".migration\" do\n    it \"returns a new migration\" do\n      migration = Hanami::Model.migration {}\n\n      expect(migration).to be_kind_of(Hanami::Model::Migration)\n    end\n  end\n\n  describe \".function\" do\n    it \"returns a database function\" do\n      function = described_class.function(:uuid_generate_v4)\n\n      expect(function).to be_kind_of(Sequel::SQL::Function)\n    end\n  end\n\n  describe \".literal\" do\n    it \"returns a database literal\" do\n      literal = described_class.literal(input = \"ROW('fuzzy dice', 42, 1.99)\")\n\n      expect(literal).to be_kind_of(Sequel::LiteralString)\n      expect(literal).to eq(input)\n    end\n  end\n\n  describe \".asc\" do\n    it \"returns an asceding order clause\" do\n      clause = described_class.asc(input = :created_at)\n\n      expect(clause).to be_kind_of(Sequel::SQL::OrderedExpression)\n      expect(clause.expression).to eq(input)\n      expect(clause.descending).to be(false)\n    end\n  end\n\n  describe \".desc\" do\n    it \"returns an descending order clause\" do\n      clause = described_class.desc(input = :created_at)\n\n      expect(clause).to be_kind_of(Sequel::SQL::OrderedExpression)\n      expect(clause.expression).to eq(input)\n      expect(clause.descending).to be(true)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/unique_constraint_violation_error_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::UniqueConstraintViolationError do\n  it \"inherits from Hanami::Model::ConstraintViolationError\" do\n    expect(described_class.ancestors).to include(Hanami::Model::ConstraintViolationError)\n  end\n\n  it \"has a default error message\" do\n    expect { raise described_class }.to raise_error(described_class, \"Unique constraint has been violated\")\n  end\n\n  it \"allows custom error message\" do\n    expect { raise described_class.new(\"Ouch\") }.to raise_error(described_class, \"Ouch\")\n  end\nend\n"
  },
  {
    "path": "spec/unit/hanami/model/version_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model::VERSION\" do\n  it \"exposes version\" do\n    expect(Hanami::Model::VERSION).to eq(\"1.3.3\")\n  end\nend\n"
  }
]