Showing preview only (456K chars total). Download the full file or copy to clipboard to get everything.
Repository: hanami/model
Branch: main
Commit: f07823d79c9a
Files: 150
Total size: 414.9 KB
Directory structure:
gitextract_p2apyb1u/
├── .github/
│ ├── FUNDING.yml
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── .jrubyrc
├── .rspec
├── .rubocop.yml
├── .yardopts
├── CHANGELOG.md
├── Gemfile
├── LICENSE.md
├── README.md
├── Rakefile
├── hanami-model.gemspec
├── lib/
│ ├── hanami/
│ │ ├── entity/
│ │ │ └── schema.rb
│ │ ├── entity.rb
│ │ ├── model/
│ │ │ ├── association.rb
│ │ │ ├── associations/
│ │ │ │ ├── belongs_to.rb
│ │ │ │ ├── dsl.rb
│ │ │ │ ├── has_many.rb
│ │ │ │ ├── has_one.rb
│ │ │ │ └── many_to_many.rb
│ │ │ ├── configuration.rb
│ │ │ ├── configurator.rb
│ │ │ ├── entity_name.rb
│ │ │ ├── error.rb
│ │ │ ├── mapped_relation.rb
│ │ │ ├── mapping.rb
│ │ │ ├── migration.rb
│ │ │ ├── migrator/
│ │ │ │ ├── adapter.rb
│ │ │ │ ├── connection.rb
│ │ │ │ ├── logger.rb
│ │ │ │ ├── mysql_adapter.rb
│ │ │ │ ├── postgres_adapter.rb
│ │ │ │ └── sqlite_adapter.rb
│ │ │ ├── migrator.rb
│ │ │ ├── plugins/
│ │ │ │ ├── mapping.rb
│ │ │ │ ├── schema.rb
│ │ │ │ └── timestamps.rb
│ │ │ ├── plugins.rb
│ │ │ ├── relation_name.rb
│ │ │ ├── sql/
│ │ │ │ ├── console.rb
│ │ │ │ ├── consoles/
│ │ │ │ │ ├── abstract.rb
│ │ │ │ │ ├── mysql.rb
│ │ │ │ │ ├── postgresql.rb
│ │ │ │ │ └── sqlite.rb
│ │ │ │ ├── entity/
│ │ │ │ │ └── schema.rb
│ │ │ │ ├── types/
│ │ │ │ │ └── schema/
│ │ │ │ │ └── coercions.rb
│ │ │ │ └── types.rb
│ │ │ ├── sql.rb
│ │ │ ├── types.rb
│ │ │ └── version.rb
│ │ ├── model.rb
│ │ └── repository.rb
│ └── hanami-model.rb
├── script/
│ └── ci
└── spec/
├── integration/
│ └── hanami/
│ └── model/
│ ├── associations/
│ │ ├── belongs_to_spec.rb
│ │ ├── has_many_spec.rb
│ │ ├── has_one_spec.rb
│ │ ├── many_to_many_spec.rb
│ │ └── relation_alias_spec.rb
│ ├── migration/
│ │ ├── mysql.rb
│ │ ├── postgresql.rb
│ │ └── sqlite.rb
│ ├── migration_spec.rb
│ └── repository/
│ ├── base_spec.rb
│ ├── command_spec.rb
│ └── legacy_spec.rb
├── spec_helper.rb
├── support/
│ ├── database/
│ │ └── strategies/
│ │ ├── abstract.rb
│ │ ├── mysql.rb
│ │ ├── postgresql.rb
│ │ ├── sql.rb
│ │ └── sqlite.rb
│ ├── database.rb
│ ├── fixtures/
│ │ ├── database_migrations/
│ │ │ ├── 20150612081248_column_types.rb
│ │ │ ├── 20150612084656_default_values.rb
│ │ │ ├── 20150612093458_null_constraints.rb
│ │ │ ├── 20150612093810_column_indexes.rb
│ │ │ ├── 20150612094740_primary_keys.rb
│ │ │ ├── 20150612115204_foreign_keys.rb
│ │ │ ├── 20150612122233_table_constraints.rb
│ │ │ ├── 20150612124205_table_alterations.rb
│ │ │ ├── 20160830094800_create_users.rb
│ │ │ ├── 20160830094851_create_authors.rb
│ │ │ ├── 20160830094941_create_books.rb
│ │ │ ├── 20160830095033_create_t_operator.rb
│ │ │ ├── 20160905125728_create_source_files.rb
│ │ │ ├── 20160909150704_create_avatars.rb
│ │ │ ├── 20161104143844_create_warehouses.rb
│ │ │ ├── 20161114094644_create_products.rb
│ │ │ ├── 20170103142428_create_colors.rb
│ │ │ ├── 20170124081339_create_labels.rb
│ │ │ ├── 20170517115243_create_tokens.rb
│ │ │ ├── 20170519172332_create_categories.rb
│ │ │ └── 20171002201227_create_posts_and_comments.rb
│ │ ├── empty_migrations/
│ │ │ └── .gitkeep
│ │ └── migrations/
│ │ ├── 20160831073534_create_reviews.rb
│ │ └── 20160831090612_add_rating_to_reviews.rb
│ ├── fixtures.rb
│ ├── platform/
│ │ ├── ci.rb
│ │ ├── db.rb
│ │ ├── engine.rb
│ │ ├── matcher.rb
│ │ └── os.rb
│ ├── platform.rb
│ ├── rspec.rb
│ └── test_io.rb
└── unit/
└── hanami/
├── entity/
│ ├── automatic_schema_spec.rb
│ ├── manual_schema/
│ │ ├── base_spec.rb
│ │ ├── strict_spec.rb
│ │ └── types_spec.rb
│ ├── schema/
│ │ ├── definition_spec.rb
│ │ └── schemaless_spec.rb
│ ├── schema_spec.rb
│ └── schemaless_spec.rb
├── entity_spec.rb
└── model/
├── check_constraint_validation_error_spec.rb
├── configuration_spec.rb
├── constraint_violation_error_spec.rb
├── disconnect_spec.rb
├── error_spec.rb
├── foreign_key_constraint_violation_error_spec.rb
├── load_spec.rb
├── mapped_relation_spec.rb
├── migrator/
│ ├── adapter_spec.rb
│ ├── connection_spec.rb
│ ├── mysql.rb
│ ├── postgresql.rb
│ └── sqlite.rb
├── migrator_spec.rb
├── not_null_constraint_violation_error_spec.rb
├── sql/
│ ├── console/
│ │ ├── mysql.rb
│ │ ├── postgresql.rb
│ │ └── sqlite.rb
│ ├── console_spec.rb
│ ├── entity/
│ │ └── schema/
│ │ ├── automatic_spec.rb
│ │ └── mapping_spec.rb
│ └── schema/
│ ├── array_spec.rb
│ ├── bool_spec.rb
│ ├── date_spec.rb
│ ├── date_time_spec.rb
│ ├── decimal_spec.rb
│ ├── float_spec.rb
│ ├── hash_spec.rb
│ ├── int_spec.rb
│ ├── string_spec.rb
│ └── time_spec.rb
├── sql_spec.rb
├── unique_constraint_violation_error_spec.rb
└── version_spec.rb
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
github: hanami
================================================
FILE: .github/workflows/ci.yml
================================================
name: ci
"on":
push:
paths:
- ".github/workflows/ci.yml"
- "lib/**"
- "*.gemspec"
- "spec/**"
- "Rakefile"
- "Gemfile"
- ".rubocop.yml"
- "script/ci"
pull_request:
branches:
- main
create:
jobs:
tests:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
ruby:
- "2.7"
db:
- sqlite3
- mysql
- postgresql
env:
DB: ${{matrix.db}}
steps:
- uses: actions/checkout@v1
- name: Install package dependencies
run: "[ -e $APT_DEPS ] || sudo apt-get install -y --no-install-recommends $APT_DEPS"
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
ruby-version: ${{matrix.ruby}}
- name: Run all tests
env:
HANAMI_DATABASE_USERNAME: root
HANAMI_DATABASE_PASSWORD: root
HANAMI_DATABASE_HOST: 127.0.0.1
HANAMI_DATABASE_NAME: hanami_model
run: script/ci
services:
mysql:
image: mysql:8
env:
ALLOW_EMPTY_PASSWORD: true
MYSQL_AUTHENTICATION_PLUGIN: mysql_native_password
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: hanami_model
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=3
postgres:
image: postgres:13
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
POSTGRES_USER: root
POSTGRES_PASSWORD: root
POSTGRES_DB: hanami_model
================================================
FILE: .gitignore
================================================
*.gem
*.rbc
.bundle
.config
.DS_Store
.greenbar
.ruby-version
.yardoc
.rubocop-*
_yardoc
coverage
doc/
Gemfile.lock
InstalledFiles
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp
================================================
FILE: .jrubyrc
================================================
debug.fullTrace=true
================================================
FILE: .rspec
================================================
--color
--require spec_helper
================================================
FILE: .rubocop.yml
================================================
# Please keep AllCops, Bundler, Style, Metrics groups and then order cops
# alphabetically
inherit_from:
- https://raw.githubusercontent.com/hanami/devtools/1.3.x/.rubocop.yml
Naming/MethodParameterName:
AllowedNames:
- ci
- db
- id
- os
Layout/LineLength:
Enabled: false
Naming/RescuedExceptionsVariableName:
PreferredName: "exception"
Style/RescueStandardError:
Enabled: false
Style/DateTime:
Enabled: false
================================================
FILE: .yardopts
================================================
--protected
--private
-
LICENSE.md
lib/**/*.rb
================================================
FILE: CHANGELOG.md
================================================
# Hanami::Model
A persistence layer for Hanami
## v1.3.3 - 2021-05-22
### Fixed
- [Sean Collins] Specify dependency on BigDecimal v1.4
- [Adam Daniels] Use environment variables for PostgreSQL CLI tools
## v1.3.2 - 2019-01-31
### Fixed
- [Luca Guidi] Depend on `dry-logic` `~> 0.4.2`, `< 0.5`
## v1.3.1 - 2019-01-18
### Added
- [Luca Guidi] Official support for Ruby: MRI 2.6
- [Luca Guidi] Support `bundler` 2.0+
## v1.3.0 - 2018-10-24
## v1.3.0.beta1 - 2018-08-08
### Fixed
- [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']).`)
- [Ian Ker-Seymer] Reliably parse query params from connection string
## v1.2.0 - 2018-04-11
## v1.2.0.rc2 - 2018-04-06
## v1.2.0.rc1 - 2018-03-30
### Fixed
- [Marcello Rocha & Luca Guidi] Ensure repository relations to access database attributes via `#[]` (eg. `projects[:name].ilike("Hanami")`)
## v1.2.0.beta2 - 2018-03-23
## v1.2.0.beta1 - 2018-02-28
### Added
- [Luca Guidi] Official support for Ruby: MRI 2.5
- [Marcello Rocha] Introduce `Hanami::Repository#command` as a factory for custom database commands. This is useful to create custom bulk operations.
## v1.1.0 - 2017-10-25
### Fixed
- [Luca Guidi] Ensure associations to always accept objects that are serializable into `::Hash`
## v1.1.0.rc1 - 2017-10-16
### Added
- [Marcello Rocha] Added support for associations aliasing via `:as` option (`has_many :users, through: :comments, as: :authors`)
- [Luca Guidi] Allow entities to be used as type in entities manual schema (`attribute :owner, Types::Entity(User)`)
## v1.1.0.beta3 - 2017-10-04
## v1.1.0.beta2 - 2017-10-03
### Added
- [Alfonso Uceda] Introduce `Hanami::Model::Migrator#rollback` to provide database migrations rollback
- [Alfonso Uceda] Improve connection string for PostgreSQL in order to pass credentials as URI query string
### Fixed
- [Marcello Rocha] One-To-Many properly destroy the associated methods
## v1.1.0.beta1 - 2017-08-11
### Added
- [Marcello Rocha] Many-To-One association (aka `belongs_to`)
- [Marcello Rocha] One-To-One association (aka `has_one`)
- [Marcello Rocha] Many-To-Many association (aka `has_many :through`)
- [Luca Guidi] Introduced new extra behaviors for entity manual schema: `:schema` (default), `:strict`, `:weak`, and `:permissive`
### Fixed
- [Sean Collins] Enhanced error message for Postgres `db create` and `db drop` when `createdb` and `dropdb` aren't in `PATH`
## v1.0.4 - 2017-10-14
### Fixed
- [Nikita Shilnikov] Keep the dependency on `rom-sql` at `~> 1.3`, which is compatible with `dry-types` `~> 0.11.0`
- [Nikita Shilnikov] Ensure to write Postgres JSON (`PGJSON`) type for nested associated records
- [Nikita Shilnikov] Ensure `Repository#select` to work with `Hanami::Model::MappedRelation`
## v1.0.3 - 2017-10-11
### Fixed
- [Luca Guidi] Keep the dependency on `dry-types` at `~> 0.11.0`
## v1.0.2 - 2017-08-04
### Fixed
- [Maurizio De Magnis] URI escape for Postgres password
- [Marion Duprey] Ensure repository to generate timestamps values even when only one between `created_at` and `updated_at` is present
- [Paweł Świątkowski] Make Postgres JSON(B) to work with Ruby arrays
- [Luca Guidi] Don't remove migrations when running `Hanami::Model::Migrator#apply` fails to dump the database
## v1.0.1 - 2017-06-23
### Fixed
- [Kai Kuchenbecker & Marcello Rocha & Luca Guidi] Ensure `Hanami::Entity#initialize` to not serialize (into `Hash`) other entities passed as an argument
- [Luca Guidi] Let `Hanami::Repository.relation=` to accept strings as an argument
- [Nikita Shilnikov] Prevent stack-overflow when `Hanami::Repository#update` is called thousand times
## v1.0.0 - 2017-04-06
## v1.0.0.rc1 - 2017-03-31
## v1.0.0.beta3 - 2017-03-17
### Added
- [Luca Guidi] Introduced `Hanami::Model.disconnect` to disconnect all the active database connections
## v1.0.0.beta2 - 2017-03-02
### Added
- [Semyon Pupkov] Allow to define Postgres connection URL as `"postgresql:///mydb?host=localhost&port=6433&user=postgres&password=testpasswd"`
### Fixed
- [Marcello Rocha] Fixed migrations MySQL detection of username and password
- [Luca Guidi] Fixed migrations creation/drop of a MySQL database with a dash in the name
- [Semyon Pupkov] Ensure `db console` to work when Postgres connection URL is defined with `"postgresql://"` scheme
## v1.0.0.beta1 - 2017-02-14
### Added
- [Luca Guidi] Official support for Ruby: MRI 2.4
- [Luca Guidi] Introduced `Repository#read` to fetch from database with raw SQL string
- [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.
- [Luca Guidi & Alfonso Uceda] Added `Hanami::Model::Configuration#gateway` to configure gateway and the raw connection
- [Luca Guidi] Added `Hanami::Model::Configuration#logger` to configure a logger
- [Luca Guidi] Database operations (including migrations) print informations to standard output
### Fixed
- [Thorbjørn Hermansen] Ensure repository to not override given timestamps
- [Luca Guidi] Raise `Hanami::Model::MissingPrimaryKeyError` if `Repository#find` is ran against a database w/o a primary key
- [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`)
### Changed
- [Luca Guidi] Automap the main relation in a repository, by removing the need of use `.as(:entity)`
- [Luca Guidi] Raise an `Hanami::Model::UnknownDatabaseTypeError` when the application is loaded and there is an unknown column type in the database
## v0.7.0 - 2016-11-15
### Added
- [Luca Guidi] `Hanami::Entity` defines an automatic schema for SQL databases
– [Luca Guidi] `Hanami::Entity` attributes schema
- [Luca Guidi] Experimental support for One-To-Many association (aka `has_many`)
- [Luca Guidi] Native support for PostgreSQL types like UUID, Array, JSON(B) and Money
- [Luca Guidi] Repositories instances can access all the relations (eg. `BookRepository` can access `users` relation via `#users`)
- [Luca Guidi] Automapping for SQL databases
- [Luca Guidi] Added `Hanami::Model::DatabaseError`
### Changed
- [Luca Guidi] Entities are immutable
- [Luca Guidi] Removed support for Memory and File System adapters
- [Luca Guidi] Removed support for _dirty tracking_
- [Luca Guidi] `Hanami::Entity.attributes` method no longer accepts a list of attributes, but a block to optionally define typed attributes
- [Luca Guidi] Removed `#fetch`, `#execute` and `#transaction` from repository
- [Luca Guidi] Removed `mapping` block from `Hanami::Model.configure`
- [Luca Guidi] Changed `adapter` signature in `Hanami::Model.configure` (use `adapter :sql, ENV['DATABASE_URL']`)
- [Luca Guidi] Repositories must inherit from `Hanami::Repository` instead of including it
- [Luca Guidi] Entities must inherit from `Hanami::Entity` instead of including it
- [Pascal Betz] Repositories use instance level interface (eg. `BookRepository.new.find` instead of `BookRepository.find`)
- [Luca Guidi] Repositories now accept hashes for CRUD operations
- [Luca Guidi] `Hanami::Repository#create` now accepts: hash (or entity)
- [Luca Guidi] `Hanami::Repository#update` now accepts two arguments: primary key (`id`) and data (or entity)
- [Luca Guidi] `Hanami::Repository#delete` now accepts: primary key (`id`)
- [Luca Guidi] Drop `Hanami::Model::NonPersistedEntityError`, `Hanami::Model::InvalidMappingError`, `Hanami::Model::InvalidCommandError`, `Hanami::Model::InvalidQueryError`
- [Luca Guidi] Official support for Ruby 2.3 and JRuby 9.0.5.0
- [Luca Guidi] Drop support for Ruby 2.0, 2.1, 2.2, and JRuby 9.0.0.0
- [Luca Guidi] Drop support for `mysql` gem in favor of `mysql2`
### Fixed
- [Luca Guidi] Ensure booleans to be correctly dumped in database
- [Luca Guidi] Ensure to respect default database schema values
- [Luca Guidi] Ensure SQL UPDATE to not override non-default primary key
- [James Hamilton] Print appropriate error message when trying to create a PostgreSQL database that is already existing
## v0.6.2 - 2016-06-01
### Changed
- [Kjell-Magne Øierud] Ensure inherited entities to expose attributes from base class
## v0.6.1 - 2016-02-05
### Changed
- [Hélio Costa e Silva & Pascal Betz] Mapping SQL Adapter's errors as `Hanami::Model` errors
## v0.6.1 - 2016-02-05
### Changed
- [Hélio Costa e Silva & Pascal Betz] Mapping SQL Adapter's errors as `Hanami::Model` errors
## v0.6.0 - 2016-01-22
### Changed
- [Luca Guidi] Renamed the project
## v0.5.2 - 2016-01-19
### Changed
- [Sean Collins] Improved error message for `Lotus::Model::Adapters::NoAdapterError`
### Fixed
- [Kyle Chong & Trung Lê] Catch Sequel exceptions and re-raise as `Lotus::Model::Error`
## v0.5.1 - 2016-01-12
### Added
- [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) }`)
### Changed
- [Andrey Deryabin] Improved `Entity#inspect`
- [Karim Tarek] Introduced `Lotus::Model::Error` and let all the framework exceptions to inherit from it.
### Fixed
- [Luca Guidi] Improved error message when trying to use a repository without mapping the corresponding collections
- [Sean Collins] Improved error message when trying to create database, but it fails (eg. missing `createdb` executable)
- [Andrey Deryabin] Improved error message when trying to drop database, but a client is still connected (useful for PostgreSQL)
- [Hiếu Nguyễn] Improved error message when trying to "prepare" database, but it fails
## v0.5.0 - 2015-09-30
### Added
- [Brenno Costa] Official support for JRuby 9k+
- [Luca Guidi] Command/Query separation via `Repository.execute` and `Repository.fetch`
- [Luca Guidi] Custom attribute coercers for data mapper
- [Alfonso Uceda] Added `#join` and `#left_join` and `#group` to SQL adapter
### Changed
- [Luca Guidi] `Repository.execute` no longer returns a result from the database.
### Fixed
- [Manuel Corrales] Use `dropdb` to drop PostgreSQL database.
- [Luca Guidi & Bohdan V.] Ignore dotfiles while running migrations.
## v0.4.1 - 2015-07-10
### Fixed
- [Nick Coyne] Fixed database creation for PostgreSQL (now it uses `createdb`).
## v0.4.0 - 2015-06-23
### Added
- [Luca Guidi] Database migrations
### Changed
- [Matthew Bellantoni] Made `Repository.execute` not callable from the outside (private Ruby method, public API).
## v0.3.2 - 2015-05-22
### Added
- [Dmitry Tymchuk & Luca Guidi] Fix for dirty tracking of attributes changed in place (eg. `book.tags << 'non-fiction'`)
## v0.3.1 - 2015-05-15
### Added
- [Dmitry Tymchuk] Dirty tracking for entities (via `Lotus::Entity::DirtyTracking` module to include)
- [My Mai] Automatic update of timestamps when an entity is persisted.
- [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'"`)
- [Guilherme Franco] Memory and File System adapters now accept a block for `where`, `or`, `and` conditions (eg `where { age > 33 }`).
### Fixed
- [Luca Guidi] Ensure Array coercion to preserve original data structure
## v0.3.0 - 2015-03-23
### Added
- [Linus Pettersson] Database console
### Fixed
- [Alfonso Uceda Pompa] Don't send unwanted null values to the database, while coercing entities
- [Jan Lelis] Do not define top-level `Boolean`, because it is already defined by `hanami-utils`
- [Vsevolod Romashov] Fix entity class resolving in `Coercer#from_record`
- [Jason Harrelson] Add file and line to `instance_eval` in `Coercer` to make backtrace more usable
## v0.2.4 - 2015-02-20
### Fixed
- [Luca Guidi] When duplicate the framework don't copy over the original `Lotus::Model` configuration
## v0.2.3 - 2015-02-13
### Added
- [Alfonso Uceda Pompa] Added support for database transactions in repositories
### Fixed
- [Luca Guidi] Ensure file system adapter old data is read when a new process is started
## v0.2.2 - 2015-01-18
### Added
- [Luca Guidi] Coerce entities when persisted
## v0.2.1 - 2015-01-12
### Added
- [Luca Guidi] Compatibility between Lotus::Entity and Lotus::Validations
## v0.2.0 - 2014-12-23
### Added
- [Luca Guidi] Introduced file system adapter
– [Benny Klotz & Trung Lê] Introduced `Entity` inheritance of attributes
- [Trung Lê] Introduced `Entity#update` for bulk update of attributes
- [Luca Guidi] Improved error when try to use a repository which wasn't configured or when the framework wasn't loaded yet
- [Trung Lê] Introduced `Entity#to_h`
- [Trung Lê] Introduced `Lotus::Model.duplicate`
- [Trung Lê] Made `Lotus::Mapper` lazy
- [Trung Lê] Introduced thread safe autoloading for adapters
- [Felipe Sere] Add support for `Symbol` coercion
- [Celso Fernandes] Add support for `BigDecimal` coercion
- [Trung Lê] Introduced `Lotus::Model.load!` as entry point for loading
- [Trung Lê] Introduced `Mapper#repository` as DSL to associate a repository to a collection
- [Trung Lê & Tao Guo] Introduced `Configuration#mapping` as DSL to configure the mapping
- [Coen Wessels] Allow `where`, `exclude` and `or` to accept blocks
- [Trung Lê & Tao Guo] Introduced `Configuration#adapter` as DSL to configure the adapter
- [Trung Lê] Introduced `Lotus::Model::Configuration`
### Changed
- [Trung Lê] Changed `Entity.attributes=` to `Entity.attributes`
- [Trung Lê] In case of missing entity, let `Repository#find` returns `nil` instead of raise an exception
### Fixed
- [Rik Tonnard] Ensure correct behavior of `#offset` in memory adapter
- [Benny Klotz] Ensure `Entity` to set the attributes even when the given Hash uses strings as keys
- [Ben Askins] Always return the entity from `Repository#persist`
- [Jeremy Stephens] Made `Memory::Query#where` and `#or` behave more like the SQL counter-part
## v0.1.2 - 2014-06-26
### Fixed
- [Stanislav Spiridonov] Ensure to require `'hanami/model/mapping/coercions'`
- [Krzysztof Zalewski] `Entity` defines `#id` accessor by default
## v0.1.1 - 2014-06-23
### Added
- [Luca Guidi] Introduced `Lotus::Model::Mapping::Coercions` in order to decouple from `Lotus::Utils::Kernel`
- [Luca Guidi] Official support for Ruby 2.1
## v0.1.0 - 2014-04-23
### Added
- [Luca Guidi] Allow to inject coercer into mapper
- [Luca Guidi] Introduced database mapping
- [Luca Guidi] Introduced `Lotus::Entity`
- [Luca Guidi] Introduced SQL adapter
- [Luca Guidi] Introduced memory adapter
– [Luca Guidi] Introduced adapters for repositories
- [Luca Guidi] Introduced `Lotus::Repository`
================================================
FILE: Gemfile
================================================
# frozen_string_literal: true
source "https://rubygems.org"
gemspec
unless ENV["CI"]
gem "byebug", require: false, platforms: :mri
gem "yard", require: false
end
gem "hanami-utils", "~> 1.3", require: false, git: "https://github.com/hanami/utils.git", branch: "1.3.x"
gem "sqlite3", require: false, platforms: :mri, group: :sqlite
gem "pg", require: false, platforms: :mri, group: :postgres
gem "mysql2", require: false, platforms: :mri, group: :mysql
gem "jdbc-sqlite3", require: false, platforms: :jruby, group: :sqlite
gem "jdbc-postgres", require: false, platforms: :jruby, group: :postgres
gem "jdbc-mysql", require: false, platforms: :jruby, group: :mysql
gem "hanami-devtools", require: false, git: "https://github.com/hanami/devtools.git", branch: "1.3.x"
================================================
FILE: LICENSE.md
================================================
Copyright © 2014-2021 Luca Guidi
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# Hanami::Model (deprecated)
## _Important notice_
**NOTE**: Hanami::Model was the persistence layer for Hanami 1.x. This library will not receive any updates.
For the persistence layer for Hanami 2.x, please see [`hanami/db`](https://github.com/hanami/db)
## Contact
* Home page: http://hanamirb.org
* Mailing List: http://hanamirb.org/mailing-list
* API Doc: http://rubydoc.info/gems/hanami-model
* Chat: https://chat.hanamirb.org
## Rubies
`Hanami::Model` **supports Hanami 1.x only**, and Ruby (MRI) 2.6 and 2.7.
## Installation
Add this line to your application's Gemfile:
```ruby
gem 'hanami-model'
```
And then execute:
$ bundle
Or install it yourself as:
$ gem install hanami-model
## Usage
This class provides a DSL to configure the connection.
```ruby
require 'hanami/model'
require 'hanami/model/sql'
class User < Hanami::Entity
end
class UserRepository < Hanami::Repository
end
Hanami::Model.configure do
adapter :sql, 'postgres://username:password@localhost/bookshelf'
end.load!
repository = UserRepository.new
user = repository.create(name: 'Luca')
puts user.id # => 1
found = repository.find(user.id)
found == user # => true
updated = repository.update(user.id, age: 34)
updated.age # => 34
repository.delete(user.id)
```
## Concepts
### Entities
A model domain object that is defined by its identity.
See "Domain Driven Design" by Eric Evans.
An entity is the core of an application, where the part of the domain logic is implemented.
It's a small, cohesive object that expresses coherent and meaningful behaviors.
It deals with one and only one responsibility that is pertinent to the
domain of the application, without caring about details such as persistence
or validations.
This simplicity of design allows developers to focus on behaviors, or
message passing if you will, which is the quintessence of Object Oriented Programming.
```ruby
require 'hanami/model'
class Person < Hanami::Entity
end
```
### Repositories
An object that mediates between entities and the persistence layer.
It offers a standardized API to query and execute commands on a database.
A repository is **storage independent**, all the queries and commands are
delegated to the current adapter.
This architecture has several advantages:
* Applications depend on a standard API, instead of low level details
(Dependency Inversion principle)
* Applications depend on a stable API, that doesn't change if the
storage changes
* Developers can postpone storage decisions
* Confines persistence logic at a low level
* Multiple data sources can easily coexist in an application
When a class inherits from `Hanami::Repository`, it will receive the following interface:
* `#create(data)` – Create a record for the given data (or entity)
* `#update(id, data)` – Update the record corresponding to the given id by setting the given data (or entity)
* `#delete(id)` – Delete the record corresponding to the given id
* `#all` - Fetch all the entities from the relation
* `#find` - Fetch an entity from the relation by primary key
* `#first` - Fetch the first entity from the relation
* `#last` - Fetch the last entity from the relation
* `#clear` - Delete all the records from the relation
**A relation is a homogenous set of records.**
It corresponds to a table for a SQL database or to a MongoDB collection.
**All the queries are private**.
This decision forces developers to define intention revealing API, instead of leaking storage API details outside of a repository.
Look at the following code:
```ruby
ArticleRepository.new.where(author_id: 23).order(:published_at).limit(8)
```
This is **bad** for a variety of reasons:
* The caller has an intimate knowledge of the internal mechanisms of the Repository.
* The caller works on several levels of abstraction.
* It doesn't express a clear intent, it's just a chain of methods.
* The caller can't be easily tested in isolation.
* If we change the storage, we are forced to change the code of the caller(s).
There is a better way:
```ruby
require 'hanami/model'
class ArticleRepository < Hanami::Repository
def most_recent_by_author(author, limit: 8)
articles.where(author_id: author.id).
order(:published_at).
limit(limit)
end
end
```
This is a **huge improvement**, because:
* The caller doesn't know how the repository fetches the entities.
* The caller works on a single level of abstraction. It doesn't even know about records, only works with entities.
* It expresses a clear intent.
* The caller can be easily tested in isolation. It's just a matter of stubbing this method.
* If we change the storage, the callers aren't affected.
### Mapping
Hanami::Model can **_automap_** columns from relations and entities attributes.
When using a `sql` adapter, you must require `hanami/model/sql` before `Hanami::Model.load!` is called so the relations are loaded correctly.
However, there are cases where columns and attribute names do not match (mainly **legacy databases**).
```ruby
require 'hanami/model'
class UserRepository < Hanami::Repository
self.relation = :t_user_archive
mapping do
attribute :id, from: :i_user_id
attribute :name, from: :s_name
attribute :age, from: :i_age
end
end
```
**NOTE:** This feature should be used only when **_automapping_** fails because of the naming mismatch.
### Conventions
* A repository must be named after an entity, by appending `"Repository"` to the entity class name (eg. `Article` => `ArticleRepository`).
### Thread safety
**Hanami::Model**'s is thread safe during the runtime, but it isn't during the loading process.
The mapper compiles some code internally, so be sure to safely load it before your application starts.
```ruby
Mutex.new.synchronize do
Hanami::Model.load!
end
```
**This is not necessary when Hanami::Model is used within a Hanami application.**
## Features
### Timestamps
If an entity has the following accessors: `:created_at` and `:updated_at`, they will be automatically updated when the entity is persisted.
```ruby
require 'hanami/model'
require 'hanami/model/sql'
class User < Hanami::Entity
end
class UserRepository < Hanami::Repository
end
Hanami::Model.configure do
adapter :sql, 'postgresql://localhost/bookshelf'
end.load!
repository = UserRepository.new
user = repository.create(name: 'Luca')
puts user.created_at.to_s # => "2016-09-19 13:40:13 UTC"
puts user.updated_at.to_s # => "2016-09-19 13:40:13 UTC"
sleep 3
user = repository.update(user.id, age: 34)
puts user.created_at.to_s # => "2016-09-19 13:40:13 UTC"
puts user.updated_at.to_s # => "2016-09-19 13:40:16 UTC"
```
## Configuration
### Logging
In order to log database operations, you can configure a logger:
```ruby
Hanami::Model.configure do
# ...
logger "log/development.log", level: :debug
end
```
It accepts the following arguments:
* `stream`: a Ruby StringIO object - it can be `$stdout` or a path to file (eg. `"log/development.log"`) - Defaults to `$stdout`
* `:level`: logging level - it can be: `:debug`, `:info`, `:warn`, `:error`, `:fatal`, `:unknown` - Defaults to `:debug`
* `:formatter`: logging formatter - it can be: `:default` or `:json` - Defaults to `:default`
## Versioning
__Hanami::Model__ uses [Semantic Versioning 2.0.0](http://semver.org)
## Contributing
1. Fork it ( https://github.com/hanami/model/fork )
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request
## Copyright
Copyright © 2014-2021 Luca Guidi – Released under MIT License
This project was formerly known as Lotus (`lotus-model`).
================================================
FILE: Rakefile
================================================
# frozen_string_literal: true
require "rake"
require "bundler/gem_tasks"
require "rspec/core/rake_task"
require "hanami/devtools/rake_tasks"
namespace :spec do
RSpec::Core::RakeTask.new(:unit) do |task|
task.pattern = FileList["spec/**/*_spec.rb"]
end
end
task default: "spec:unit"
================================================
FILE: hanami-model.gemspec
================================================
# frozen_string_literal: true
lib = File.expand_path("../lib", __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "hanami/model/version"
Gem::Specification.new do |spec|
spec.name = "hanami-model"
spec.version = Hanami::Model::VERSION
spec.authors = ["Luca Guidi"]
spec.email = ["me@lucaguidi.com"]
spec.summary = "A persistence layer for Hanami"
spec.description = "A persistence framework with entities and repositories"
spec.homepage = "http://hanamirb.org"
spec.license = "MIT"
spec.files = `git ls-files -z -- lib/* CHANGELOG.md EXAMPLE.md LICENSE.md README.md hanami-model.gemspec`.split("\x0")
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ["lib"]
spec.required_ruby_version = ">= 2.3.0", "< 3"
spec.add_runtime_dependency "hanami-utils", "~> 1.3"
spec.add_runtime_dependency "rom", "~> 3.3", ">= 3.3.3"
spec.add_runtime_dependency "rom-sql", "~> 1.3", ">= 1.3.5"
spec.add_runtime_dependency "rom-repository", "~> 1.4"
spec.add_runtime_dependency "dry-types", "~> 0.11.0"
spec.add_runtime_dependency "dry-logic", "~> 0.4.2", "< 0.5"
spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
spec.add_runtime_dependency "bigdecimal", "~> 1.4"
spec.add_development_dependency "bundler", ">= 1.6", "< 3"
spec.add_development_dependency "rake", "~> 12"
spec.add_development_dependency "rspec", "~> 3.7"
spec.add_development_dependency "rubocop", "0.81" # rubocop 0.81+ removed support for Ruby 2.3
end
================================================
FILE: lib/hanami/entity/schema.rb
================================================
# frozen_string_literal: true
require "hanami/model/types"
require "hanami/utils/hash"
module Hanami
class Entity
# Entity schema is a definition of a set of typed attributes.
#
# @since 0.7.0
# @api private
#
# @example SQL Automatic Setup
# require 'hanami/model'
#
# class Account < Hanami::Entity
# end
#
# account = Account.new(name: "Acme Inc.")
# account.name # => "Hanami"
#
# account = Account.new(foo: "bar")
# account.foo # => NoMethodError
#
# @example Non-SQL Manual Setup
# require 'hanami/model'
#
# class Account < Hanami::Entity
# attributes do
# attribute :id, Types::Int
# attribute :name, Types::String
# attribute :codes, Types::Array(Types::Int)
# attribute :users, Types::Array(User)
# attribute :email, Types::String.constrained(format: /@/)
# attribute :created_at, Types::DateTime
# end
# end
#
# account = Account.new(name: "Acme Inc.")
# account.name # => "Acme Inc."
#
# account = Account.new(foo: "bar")
# account.foo # => NoMethodError
#
# @example Schemaless Entity
# require 'hanami/model'
#
# class Account < Hanami::Entity
# end
#
# account = Account.new(name: "Acme Inc.")
# account.name # => "Acme Inc."
#
# account = Account.new(foo: "bar")
# account.foo # => "bar"
class Schema
# Schemaless entities logic
#
# @since 0.7.0
# @api private
class Schemaless
# @since 0.7.0
# @api private
def initialize
freeze
end
# @param attributes [#to_hash] the attributes hash
#
# @return [Hash]
#
# @since 0.7.0
# @api private
def call(attributes)
if attributes.nil?
{}
else
Utils::Hash.deep_symbolize(attributes.to_hash.dup)
end
end
# @since 0.7.0
# @api private
def attribute?(_name)
true
end
end
# Schema definition
#
# @since 0.7.0
# @api private
class Definition
# Schema DSL
#
# @since 0.7.0
class Dsl
# @since 1.1.0
# @api private
TYPES = %i[schema strict weak permissive strict_with_defaults symbolized].freeze
# @since 1.1.0
# @api private
DEFAULT_TYPE = TYPES.first
# @since 0.7.0
# @api private
def self.build(type, &blk)
type ||= DEFAULT_TYPE
raise Hanami::Model::Error.new("Unknown schema type: `#{type.inspect}'") unless TYPES.include?(type)
attributes = new(&blk).to_h
[attributes, Hanami::Model::Types::Coercible::Hash.__send__(type, attributes)]
end
# @since 0.7.0
# @api private
def initialize(&blk)
@attributes = {}
instance_eval(&blk)
end
# Define an attribute
#
# @param name [Symbol] the attribute name
# @param type [Dry::Types::Definition] the attribute type
#
# @since 0.7.0
#
# @example
# require 'hanami/model'
#
# class Account < Hanami::Entity
# attributes do
# attribute :id, Types::Int
# attribute :name, Types::String
# attribute :codes, Types::Array(Types::Int)
# attribute :users, Types::Array(User)
# attribute :email, Types::String.constrained(format: /@/)
# attribute :created_at, Types::DateTime
# end
# end
#
# account = Account.new(name: "Acme Inc.")
# account.name # => "Acme Inc."
#
# account = Account.new(foo: "bar")
# account.foo # => NoMethodError
def attribute(name, type)
@attributes[name] = type
end
# @since 0.7.0
# @api private
def to_h
@attributes
end
end
# Instantiate a new DSL instance for an entity
#
# @param blk [Proc] the block that defines the attributes
#
# @return [Hanami::Entity::Schema::Dsl] the DSL
#
# @since 0.7.0
# @api private
def initialize(type = nil, &blk)
raise LocalJumpError unless block_given?
@attributes, @schema = Dsl.build(type, &blk)
@attributes = Hash[@attributes.map { |k, _| [k, true] }]
freeze
end
# Process attributes
#
# @param attributes [#to_hash] the attributes hash
#
# @raise [TypeError] if the process fails
# @raise [ArgumentError] if data is missing, or unknown keys are given
#
# @since 0.7.0
# @api private
def call(attributes)
schema.call(attributes)
rescue Dry::Types::SchemaError => exception
raise TypeError.new(exception.message)
rescue Dry::Types::MissingKeyError, Dry::Types::UnknownKeysError => exception
raise ArgumentError.new(exception.message)
end
# Check if the attribute is known
#
# @param name [Symbol] the attribute name
#
# @return [TrueClass,FalseClass] the result of the check
#
# @since 0.7.0
# @api private
def attribute?(name)
attributes.key?(name)
end
private
# @since 0.7.0
# @api private
attr_reader :schema
# @since 0.7.0
# @api private
attr_reader :attributes
end
# Build a new instance of Schema with the attributes defined by the given block
#
# @param blk [Proc] the optional block that defines the attributes
#
# @return [Hanami::Entity::Schema] the schema
#
# @since 0.7.0
# @api private
def initialize(type = nil, &blk)
@schema = if block_given?
Definition.new(type, &blk)
else
Schemaless.new
end
end
# Process attributes
#
# @param attributes [#to_hash] the attributes hash
#
# @raise [TypeError] if the process fails
#
# @since 0.7.0
# @api private
def call(attributes)
Utils::Hash.deep_symbolize(
schema.call(attributes)
)
end
# @since 0.7.0
# @api private
alias_method :[], :call
# Check if the attribute is known
#
# @param name [Symbol] the attribute name
#
# @return [TrueClass,FalseClass] the result of the check
#
# @since 0.7.0
# @api private
def attribute?(name)
schema.attribute?(name)
end
protected
# @since 0.7.0
# @api private
attr_reader :schema
end
end
end
================================================
FILE: lib/hanami/entity.rb
================================================
# frozen_string_literal: true
require "hanami/model/types"
module Hanami
# An object that is defined by its identity.
# See "Domain Driven Design" by Eric Evans.
#
# An entity is the core of an application, where the part of the domain
# logic is implemented. It's a small, cohesive object that expresses coherent
# and meaningful behaviors.
#
# It deals with one and only one responsibility that is pertinent to the
# domain of the application, without caring about details such as persistence
# or validations.
#
# This simplicity of design allows developers to focus on behaviors, or
# message passing if you will, which is the quintessence of Object Oriented
# Programming.
#
# @example With Hanami::Entity
# require 'hanami/model'
#
# class Person < Hanami::Entity
# end
#
# If we expand the code above in **pure Ruby**, it would be:
#
# @example Pure Ruby
# class Person
# attr_accessor :id, :name, :age
#
# def initialize(attributes = {})
# @id, @name, @age = attributes.values_at(:id, :name, :age)
# end
# end
#
# **Hanami::Model** ships `Hanami::Entity` for developers' convenience.
#
# **Hanami::Model** depends on a narrow and well-defined interface for an
# Entity - `#id`, `#id=`, `#initialize(attributes={})`.If your object
# implements that interface then that object can be used as an Entity in the
# **Hanami::Model** framework.
#
# However, we suggest to implement this interface by including
# `Hanami::Entity`, in case that future versions of the framework will expand
# it.
#
# See Dependency Inversion Principle for more on interfaces.
#
# @since 0.1.0
#
# @see Hanami::Repository
class Entity
require "hanami/entity/schema"
# Syntactic shortcut to reference types in custom schema DSL
#
# @since 0.7.0
module Types
include Hanami::Model::Types
end
# Class level interface
#
# @since 0.7.0
# @api private
module ClassMethods
# Define manual entity schema
#
# With a SQL database this setup happens automatically and you SHOULD NOT
# use this DSL. You should use only when you want to customize the automatic
# setup.
#
# If you're working with an entity that isn't "backed" by a SQL table or
# with a schema-less database, you may want to manually setup a set of
# attributes via this DSL. If you don't do any setup, the entity accepts all
# the given attributes.
#
# @param type [Symbol] the type of schema to build
# @param blk [Proc] the block that defines the attributes
#
# @since 0.7.0
#
# @see Hanami::Entity
def attributes(type = nil, &blk)
self.schema = Schema.new(type, &blk)
@attributes = true
end
# Assign a schema
#
# @param value [Hanami::Entity::Schema] the schema
#
# @since 0.7.0
# @api private
def schema=(value)
return if defined?(@attributes)
@schema = value
end
# @since 0.7.0
# @api private
attr_reader :schema
end
# @since 0.7.0
# @api private
def self.inherited(klass)
klass.class_eval do
@schema = Schema.new
extend ClassMethods
end
end
# Instantiate a new entity
#
# @param attributes [Hash,#to_h,NilClass] data to initialize the entity
#
# @return [Hanami::Entity] the new entity instance
#
# @raise [TypeError] if the given attributes are invalid
#
# @since 0.1.0
def initialize(attributes = nil)
@attributes = self.class.schema[attributes]
freeze
end
# Entity ID
#
# @return [Object,NilClass] the ID, if present
#
# @since 0.7.0
def id
attributes.fetch(:id, nil)
end
# Handle dynamic accessors
#
# If internal attributes set has the requested key, it returns the linked
# value, otherwise it raises a <tt>NoMethodError</tt>
#
# @since 0.7.0
def method_missing(method_name, *)
attribute?(method_name) or super
attributes.fetch(method_name, nil)
end
# Implement generic equality for entities
#
# Two entities are equal if they are instances of the same class and they
# have the same id.
#
# @param other [Object] the object of comparison
#
# @return [FalseClass,TrueClass] the result of the check
#
# @since 0.1.0
def ==(other)
self.class == other.class &&
id == other.id
end
# Implement predictable hashing for hash equality
#
# @return [Integer] the object hash
#
# @since 0.7.0
def hash
[self.class, id].hash
end
# Freeze the entity
#
# @since 0.7.0
def freeze
attributes.freeze
super
end
# Serialize entity to a Hash
#
# @return [Hash] the result of serialization
#
# @since 0.1.0
def to_h
Utils::Hash.deep_dup(attributes)
end
# @since 0.7.0
alias_method :to_hash, :to_h
protected
# Check if the attribute is allowed to be read
#
# @since 0.7.0
# @api private
def attribute?(name)
self.class.schema.attribute?(name)
end
private
# @since 0.1.0
# @api private
attr_reader :attributes
# @since 0.7.0
# @api private
def respond_to_missing?(name, _include_all)
attribute?(name)
end
end
end
================================================
FILE: lib/hanami/model/association.rb
================================================
# frozen_string_literal: true
require "rom-sql"
require "hanami/model/associations/belongs_to"
require "hanami/model/associations/has_many"
require "hanami/model/associations/has_one"
require "hanami/model/associations/many_to_many"
module Hanami
module Model
# Association factory
#
# @since 0.7.0
# @api private
class Association
# Instantiate an association
#
# @since 0.7.0
# @api private
def self.build(repository, target, subject)
lookup(repository.root.associations[target])
.new(repository, repository.root.name.to_sym, target, subject)
end
# Translate ROM SQL associations into Hanami::Model associations
#
# @since 0.7.0
# @api private
def self.lookup(association)
case association
when ROM::SQL::Association::ManyToMany
Associations::ManyToMany
when ROM::SQL::Association::OneToOne
Associations::HasOne
when ROM::SQL::Association::OneToMany
Associations::HasMany
when ROM::SQL::Association::ManyToOne
Associations::BelongsTo
else
raise "Unsupported association: #{association}"
end
end
end
end
end
================================================
FILE: lib/hanami/model/associations/belongs_to.rb
================================================
# frozen_string_literal: true
require "hanami/model/types"
module Hanami
module Model
module Associations
# Many-To-One association
#
# @since 1.1.0
# @api private
class BelongsTo
# @since 1.1.0
# @api private
def self.schema_type(entity)
Sql::Types::Schema::AssociationType.new(entity)
end
# @since 1.1.0
# @api private
attr_reader :repository
# @since 1.1.0
# @api private
attr_reader :source
# @since 1.1.0
# @api private
attr_reader :target
# @since 1.1.0
# @api private
attr_reader :subject
# @since 1.1.0
# @api private
attr_reader :scope
# @since 1.1.0
# @api private
def initialize(repository, source, target, subject, scope = nil)
@repository = repository
@source = source
@target = target
@subject = subject.to_hash unless subject.nil?
@scope = scope || _build_scope
freeze
end
# @since 1.1.0
# @api private
def one
scope.one
end
private
# @since 1.1.0
# @api private
def container
repository.container
end
# @since 1.1.0
# @api private
def primary_key
association_keys.first
end
# @since 1.1.0
# @api private
def relation(name)
repository.relations[Hanami::Utils::String.pluralize(name)]
end
# @since 1.1.0
# @api private
def foreign_key
association_keys.last
end
# Returns primary key and foreign key
#
# @since 1.1.0
# @api private
def association_keys
association
.__send__(:join_key_map, container.relations)
end
# Return the ROM::Associations for the source relation
#
# @since 1.1.9
# @api private
def association
relation(source).associations[target]
end
# @since 1.1.0
# @api private
def _build_scope
result = relation(association.target.to_sym)
result = result.where(foreign_key => subject.fetch(primary_key)) unless subject.nil?
result.as(Model::MappedRelation.mapper_name)
end
end
end
end
end
================================================
FILE: lib/hanami/model/associations/dsl.rb
================================================
# frozen_string_literal: true
module Hanami
module Model
module Associations
# Auto-infer relations linked to repository's associations
#
# @since 0.7.0
# @api private
#
class Dsl
# @since 0.7.0
# @api private
def initialize(repository, &blk)
@repository = repository
instance_eval(&blk)
end
# @since 0.7.0
# @api private
def has_many(relation, **args)
@repository.__send__(:relations, relation)
@repository.__send__(:relations, args[:through]) if args[:through]
end
# @since 1.1.0
# @api private
def has_one(relation, *)
@repository.__send__(:relations, Hanami::Utils::String.pluralize(relation).to_sym)
end
# @since 1.1.0
# @api private
def belongs_to(relation, *)
@repository.__send__(:relations, Hanami::Utils::String.pluralize(relation).to_sym)
end
end
end
end
end
================================================
FILE: lib/hanami/model/associations/has_many.rb
================================================
# frozen_string_literal: true
require "hanami/model/types"
module Hanami
module Model
module Associations
# One-To-Many association
#
# @since 0.7.0
# @api private
class HasMany
# @since 0.7.0
# @api private
def self.schema_type(entity)
type = Sql::Types::Schema::AssociationType.new(entity)
Types::Strict::Array.member(type)
end
# @since 0.7.0
# @api private
attr_reader :repository
# @since 0.7.0
# @api private
attr_reader :source
# @since 0.7.0
# @api private
attr_reader :target
# @since 0.7.0
# @api private
attr_reader :subject
# @since 0.7.0
# @api private
attr_reader :scope
# @since 0.7.0
# @api private
def initialize(repository, source, target, subject, scope = nil)
@repository = repository
@source = source
@target = target
@subject = subject.to_hash unless subject.nil?
@scope = scope || _build_scope
freeze
end
# @since 0.7.0
# @api private
def create(data)
entity.new(command(:create, aggregate(target), mapper: nil, use: [:timestamps])
.call(serialize(data)))
rescue => exception
raise Hanami::Model::Error.for(exception)
end
# @since 0.7.0
# @api private
def add(data)
command(:create, relation(target), use: [:timestamps])
.call(associate(serialize(data)))
rescue => exception
raise Hanami::Model::Error.for(exception)
end
# @since 0.7.0
# @api private
def remove(id)
command(:update, relation(target), use: [:timestamps])
.by_pk(id)
.call(unassociate)
end
# @since 0.7.0
# @api private
def delete
scope.delete
end
# @since 0.7.0
# @api private
def each(&blk)
scope.each(&blk)
end
# @since 0.7.0
# @api private
def map(&blk)
to_a.map(&blk)
end
# @since 0.7.0
# @api private
def to_a
scope.to_a
end
# @since 0.7.0
# @api private
def where(condition)
__new__(scope.where(condition))
end
# @since 0.7.0
# @api private
def count
scope.count
end
private
# @since 0.7.0
# @api private
def command(target, relation, options = {})
repository.command(target, relation, options)
end
# @since 0.7.0
# @api private
def entity
repository.class.entity
end
# @since 0.7.0
# @api private
def relation(name)
repository.relations[name]
end
# @since 0.7.0
# @api private
def aggregate(name)
repository.aggregate(name)
end
# @since 0.7.0
# @api private
def association(name)
relation(target).associations[name]
end
# @since 0.7.0
# @api private
def associate(data)
relation(source)
.associations[target]
.associate(container.relations, data, subject)
end
# @since 0.7.0
# @api private
def unassociate
{foreign_key => nil}
end
# @since 0.7.0
# @api private
def container
repository.container
end
# @since 0.7.0
# @api private
def primary_key
association_keys.first
end
# @since 0.7.0
# @api private
def foreign_key
association_keys.last
end
# Returns primary key and foreign key
#
# @since 0.7.0
# @api private
def association_keys
target_association
.__send__(:join_key_map, container.relations)
end
# Returns the targeted association for a given source
#
# @since 0.7.0
# @api private
def target_association
relation(source).associations[target]
end
# @since 0.7.0
# @api private
def _build_scope
result = relation(target_association.target.to_sym)
result = result.where(foreign_key => subject.fetch(primary_key)) unless subject.nil?
result.as(Model::MappedRelation.mapper_name)
end
# @since 0.7.0
# @api private
def __new__(new_scope)
self.class.new(repository, source, target, subject, new_scope)
end
def serialize(data)
Utils::Hash.deep_serialize(data)
end
end
end
end
end
================================================
FILE: lib/hanami/model/associations/has_one.rb
================================================
# frozen_string_literal: true
require "hanami/utils/hash"
module Hanami
module Model
module Associations
# Many-To-One association
#
# @since 1.1.0
# @api private
class HasOne
# @since 1.1.0
# @api private
def self.schema_type(entity)
Sql::Types::Schema::AssociationType.new(entity)
end
#
# @since 1.1.0
# @api private
attr_reader :repository
# @since 1.1.0
# @api private
attr_reader :source
# @since 1.1.0
# @api private
attr_reader :target
# @since 1.1.0
# @api private
attr_reader :subject
# @since 1.1.0
# @api private
attr_reader :scope
# @since 1.1.0
# @api private
def initialize(repository, source, target, subject, scope = nil)
@repository = repository
@source = source
@target = target
@subject = subject.to_hash unless subject.nil?
@scope = scope || _build_scope
freeze
end
def one
scope.one
end
def create(data)
entity.new(
command(:create, aggregate(target), mapper: nil).call(serialize(data))
)
rescue => exception
raise Hanami::Model::Error.for(exception)
end
def add(data)
command(:create, relation(target), mapper: nil).call(associate(serialize(data)))
rescue => exception
raise Hanami::Model::Error.for(exception)
end
def update(data)
command(:update, relation(target), mapper: nil)
.by_pk(
one.public_send(relation(target).primary_key)
).call(serialize(data))
rescue => exception
raise Hanami::Model::Error.for(exception)
end
def delete
scope.delete
end
alias_method :remove, :delete
def replace(data)
repository.transaction do
delete
add(serialize(data))
end
end
private
# @since 1.1.0
# @api private
def entity
repository.class.entity
end
# @since 1.1.0
# @api private
def aggregate(name)
repository.aggregate(name)
end
# @since 1.1.0
# @api private
def command(target, relation, options = {})
repository.command(target, relation, options)
end
# @since 1.1.0
# @api private
def relation(name)
repository.relations[Hanami::Utils::String.pluralize(name)]
end
# @since 1.1.0
# @api private
def container
repository.container
end
# @since 1.1.0
# @api private
def primary_key
association_keys.first
end
# @since 1.1.0
# @api private
def foreign_key
association_keys.last
end
# @since 1.1.0
# @api private
def associate(data)
relation(source)
.associations[target]
.associate(container.relations, data, subject)
end
# Returns primary key and foreign key
#
# @since 1.1.0
# @api private
def association_keys
relation(source)
.associations[target]
.__send__(:join_key_map, container.relations)
end
# @since 1.1.0
# @api private
def _build_scope
result = relation(target)
result = result.where(foreign_key => subject.fetch(primary_key)) unless subject.nil?
result.as(Model::MappedRelation.mapper_name)
end
# @since 1.1.0
# @api private
def serialize(data)
Utils::Hash.deep_serialize(data)
end
end
end
end
end
================================================
FILE: lib/hanami/model/associations/many_to_many.rb
================================================
# frozen_string_literal: true
require "hanami/utils/hash"
module Hanami
module Model
module Associations
# Many-To-Many association
#
# @since 0.7.0
# @api private
class ManyToMany
# @since 0.7.0
# @api private
def self.schema_type(entity)
type = Sql::Types::Schema::AssociationType.new(entity)
Types::Strict::Array.member(type)
end
# @since 1.1.0
# @api private
attr_reader :repository
# @since 1.1.0
# @api private
attr_reader :source
# @since 1.1.0
# @api private
attr_reader :target
# @since 1.1.0
# @api private
attr_reader :subject
# @since 1.1.0
# @api private
attr_reader :scope
# @since 1.1.0
# @api private
attr_reader :through
def initialize(repository, source, target, subject, scope = nil)
@repository = repository
@source = source
@target = target
@subject = subject.to_hash unless subject.nil?
@through = relation(source).associations[target].through.to_sym
@scope = scope || _build_scope
freeze
end
def to_a
scope.to_a
end
def map(&blk)
to_a.map(&blk)
end
def each(&blk)
scope.each(&blk)
end
def count
scope.count
end
def where(condition)
__new__(scope.where(condition))
end
# Return the association table object. Would need an aditional query to return the entity
#
# @since 1.1.0
# @api private
def add(*data)
command(:create, relation(through), use: [:timestamps])
.call(associate(serialize(data)))
rescue => exception
raise Hanami::Model::Error.for(exception)
end
# @since 1.1.0
# @api private
def delete
relation(through).where(source_foreign_key => subject.fetch(source_primary_key)).delete
end
# @since 1.1.0
# @api private
def remove(target_id)
association_record = relation(through)
.where(target_foreign_key => target_id, source_foreign_key => subject.fetch(source_primary_key))
.one
return if association_record.nil?
ar_id = association_record.public_send relation(through).primary_key
command(:delete, relation(through)).by_pk(ar_id).call
end
private
# @since 1.1.0
# @api private
def container
repository.container
end
# @since 1.1.0
# @api private
def relation(name)
repository.relations[name]
end
# @since 1.1.0
# @api private
def command(target, relation, options = {})
repository.command(target, relation, options)
end
# @since 1.1.0
# @api private
def associate(data)
relation(target)
.associations[source]
.associate(container.relations, data, subject)
end
# @since 1.1.0
# @api private
def source_primary_key
association_keys[0].first
end
# @since 1.1.0
# @api private
def source_foreign_key
association_keys[0].last
end
# @since 1.1.0
# @api private
def association_keys
relation(source)
.associations[target]
.__send__(:join_key_map, container.relations)
end
# @since 1.1.0
# @api private
def target_foreign_key
association_keys[1].first
end
# @since 1.1.0
# @api private
def target_primary_key
association_keys[1].last
end
# Return the ROM::Associations for the source relation
#
# @since 1.1.0
# @api private
def association
relation(source).associations[target]
end
# @since 1.1.0
#
# @api private
def _build_scope
result = relation(association.target.to_sym).qualified
unless subject.nil?
result = result
.join(through, target_foreign_key => target_primary_key)
.where(source_foreign_key => subject.fetch(source_primary_key))
end
result.as(Model::MappedRelation.mapper_name)
end
# @since 1.1.0
# @api private
def __new__(new_scope)
self.class.new(repository, source, target, subject, new_scope)
end
# @since 1.1.0
# @api private
def serialize(data)
data.map do |d|
Utils::Hash.deep_serialize(d)
end
end
end
end
end
end
================================================
FILE: lib/hanami/model/configuration.rb
================================================
# frozen_string_literal: true
require "rom/configuration"
module Hanami
module Model
# Configuration for the framework, models and adapters.
#
# Hanami::Model has its own global configuration that can be manipulated
# via `Hanami::Model.configure`.
#
# @since 0.2.0
class Configuration
# @since 0.7.0
# @api private
attr_reader :mappings
# @since 0.7.0
# @api private
attr_reader :entities
# @since 1.0.0
# @api private
attr_reader :logger
# @since 1.0.0
# @api private
attr_reader :migrations_logger
# @since 0.2.0
# @api private
def initialize(configurator)
@backend = configurator.backend
@url = configurator.url
@migrations = configurator._migrations
@schema = configurator._schema
@gateway_config = configurator._gateway
@logger = configurator._logger
@migrations_logger = configurator.migrations_logger
@mappings = {}
@entities = {}
end
# NOTE: This must be changed when we want to support several adapters at the time
#
# @since 0.7.0
# @api private
attr_reader :url
# NOTE: This must be changed when we want to support several adapters at the time
#
# @raise [Hanami::Model::UnknownDatabaseAdapterError] if `url` is blank,
# or it uses an unknown adapter.
#
# @since 0.7.0
# @api private
def connection
gateway.connection
end
# NOTE: This must be changed when we want to support several adapters at the time
#
# @raise [Hanami::Model::UnknownDatabaseAdapterError] if `url` is blank,
# or it uses an unknown adapter.
#
# @since 0.7.0
# @api private
def gateway
gateways[:default]
end
# Root directory
#
# @since 0.4.0
# @api private
def root
Hanami.respond_to?(:root) ? Hanami.root : Pathname.pwd
end
# Migrations directory
#
# @since 0.4.0
def migrations
(@migrations.nil? ? root : root.join(@migrations)).realpath
end
# Path for schema dump file
#
# @since 0.4.0
def schema
@schema.nil? ? root : root.join(@schema)
end
# @since 0.7.0
# @api private
def define_mappings(root, &blk)
@mappings[root] = Mapping.new(&blk)
end
# @since 0.7.0
# @api private
def register_entity(plural, singular, klass)
@entities[plural] = klass
@entities[singular] = klass
end
# @since 0.7.0
# @api private
def define_entities_mappings(container, repositories)
return unless defined?(Sql::Entity::Schema)
repositories.each do |r|
relation = r.relation
entity = r.entity
entity.schema = Sql::Entity::Schema.new(entities, container.relations[relation], mappings.fetch(relation))
end
end
# @since 1.0.0
# @api private
def configure_gateway
@gateway_config&.call(gateway)
end
# @since 1.0.0
# @api private
def logger=(value)
return if value.nil?
gateway.use_logger(@logger = value)
end
# @raise [Hanami::Model::UnknownDatabaseAdapterError] if `url` is blank,
# or it uses an unknown adapter.
#
# @since 1.0.0
# @api private
def rom
@rom ||= ROM::Configuration.new(@backend, @url, infer_relations: false)
rescue => exception
raise UnknownDatabaseAdapterError.new(@url) if exception.message =~ /adapters/
raise exception
end
# @raise [Hanami::Model::UnknownDatabaseAdapterError] if `url` is blank,
# or it uses an unknown adapter.
#
# @since 1.0.0
# @api private
def load!(repositories, &blk)
rom.setup.auto_registration(config.directory.to_s) unless config.directory.nil?
rom.instance_eval(&blk) if block_given?
configure_gateway
repositories.each(&:load!)
self.logger = logger
container = ROM.container(rom)
define_entities_mappings(container, repositories)
container
rescue => exception
raise Hanami::Model::Error.for(exception)
end
# @since 1.0.0
# @api private
def method_missing(method_name, *args, &blk)
if rom.respond_to?(method_name)
rom.__send__(method_name, *args, &blk)
else
super
end
end
# @since 1.1.0
# @api private
def respond_to_missing?(method_name, include_all)
rom.respond_to?(method_name, include_all)
end
end
end
end
================================================
FILE: lib/hanami/model/configurator.rb
================================================
# frozen_string_literal: true
module Hanami
module Model
# Configuration DSL
#
# @since 0.7.0
# @api private
class Configurator
# @since 0.7.0
# @api private
attr_reader :backend
# @since 0.7.0
# @api private
attr_reader :url
# @since 0.7.0
# @api private
attr_reader :directory
# @since 0.7.0
# @api private
attr_reader :_migrations
# @since 0.7.0
# @api private
attr_reader :_schema
# @since 1.0.0
# @api private
attr_reader :_logger
# @since 1.0.0
# @api private
attr_reader :_gateway
# @since 0.7.0
# @api private
def self.build(&block)
new.tap { |config| config.instance_eval(&block) }
end
# @since 1.0.0
# @api private
def migrations_logger(stream = $stdout)
require "hanami/model/migrator/logger"
@migrations_logger ||= Hanami::Model::Migrator::Logger.new(stream)
end
private
# @since 0.7.0
# @api private
def adapter(backend, url)
@backend = backend
@url = url
end
# @since 0.7.0
# @api private
def path(path)
@directory = path
end
# @since 0.7.0
# @api private
def migrations(path)
@_migrations = path
end
# @since 0.7.0
# @api private
def schema(path)
@_schema = path
end
# @since 1.0.0
# @api private
def logger(stream, options = {})
require "hanami/logger"
opts = options.merge(stream: stream)
@_logger = Hanami::Logger.new("hanami.model", **opts)
end
# @since 1.0.0
# @api private
def gateway(&blk)
@_gateway = blk
end
end
end
end
================================================
FILE: lib/hanami/model/entity_name.rb
================================================
# frozen_string_literal: true
module Hanami
module Model
# Conventional name for entities.
#
# Given a repository named <tt>SourceFileRepository</tt>, the associated
# entity will be <tt>SourceFile</tt>.
#
# @since 0.7.0
# @api private
class EntityName
# @since 0.7.0
# @api private
SUFFIX = /Repository\z/.freeze
# @param name [Class,String] the class or its name
# @return [String] the entity name
#
# @since 0.7.0
# @api private
def initialize(name)
@name = name.sub(SUFFIX, "")
end
# @since 0.7.0
# @api private
def underscore
Utils::String.underscore(@name).to_sym
end
# @since 0.7.0
# @api private
def to_s
@name
end
end
end
end
================================================
FILE: lib/hanami/model/error.rb
================================================
# frozen_string_literal: true
require "concurrent"
module Hanami
module Model
# Default Error class
#
# @since 0.5.1
class Error < ::StandardError
# @api private
# @since 0.7.0
@__mapping__ = Concurrent::Map.new
# @api private
# @since 0.7.0
def self.for(exception)
mapping.fetch(exception.class, self).new(exception)
end
# @api private
# @since 0.7.0
def self.register(external, internal)
mapping.put_if_absent(external, internal)
end
# @api private
# @since 0.7.0
def self.mapping
@__mapping__
end
end
# Generic database error
#
# @since 0.7.0
class DatabaseError < Error
end
# Error for invalid raw command syntax
#
# @since 0.5.0
class InvalidCommandError < Error
# @since 0.5.0
# @api private
def initialize(message = "Invalid command")
super
end
end
# Error for Constraint Violation
#
# @since 0.7.0
class ConstraintViolationError < Error
# @since 0.7.0
# @api private
def initialize(message = "Constraint has been violated")
super
end
end
# Error for Unique Constraint Violation
#
# @since 0.6.1
class UniqueConstraintViolationError < ConstraintViolationError
# @since 0.6.1
# @api private
def initialize(message = "Unique constraint has been violated")
super
end
end
# Error for Foreign Key Constraint Violation
#
# @since 0.6.1
class ForeignKeyConstraintViolationError < ConstraintViolationError
# @since 0.6.1
# @api private
def initialize(message = "Foreign key constraint has been violated")
super
end
end
# Error for Not Null Constraint Violation
#
# @since 0.6.1
class NotNullConstraintViolationError < ConstraintViolationError
# @since 0.6.1
# @api private
def initialize(message = "NOT NULL constraint has been violated")
super
end
end
# Error for Check Constraint Violation raised by Sequel
#
# @since 0.6.1
class CheckConstraintViolationError < ConstraintViolationError
# @since 0.6.1
# @api private
def initialize(message = "Check constraint has been violated")
super
end
end
# Unknown database type error for repository auto-mapping
#
# @since 1.0.0
class UnknownDatabaseTypeError < Error
end
# Unknown primary key error
#
# @since 1.0.0
class MissingPrimaryKeyError < Error
end
# Unknown attribute error
#
# @since 1.2.0
class UnknownAttributeError < Error
end
# Unknown database adapter error
#
# @since 1.2.1
class UnknownDatabaseAdapterError < Error
def initialize(url)
super("Unknown database adapter for URL: #{url.inspect}. Please check your database configuration (hint: ENV['DATABASE_URL']).")
end
end
end
end
================================================
FILE: lib/hanami/model/mapped_relation.rb
================================================
# frozen_string_literal: true
module Hanami
module Model
# Mapped proxy for ROM relations.
#
# It eliminates the need to use #as for repository queries
#
# @since 1.0.0
# @api private
class MappedRelation < SimpleDelegator
# Mapper name.
#
# With ROM mapping there is a link between the entity class and a generic
# reference for it. Example: <tt>BookRepository</tt> references <tt>Book</tt>
# as <tt>:entity</tt>.
#
# @since 1.0.0
# @api private
MAPPER_NAME = :entity
# @since 1.0.0
# @api private
def self.mapper_name
MAPPER_NAME
end
# @since 1.0.0
# @api private
def initialize(relation)
@relation = relation
super(relation.as(self.class.mapper_name))
end
# Access low level relation's attribute
#
# @param attribute [Symbol] the attribute name
#
# @return [ROM::SQL::Attribute] the attribute
#
# @raise [Hanami::Model::UnknownAttributeError] if the attribute cannot be found
#
# @since 1.2.0
#
# @example
# class UserRepository < Hanami::Repository
# def by_matching_name(name)
# users
# .where(users[:name].ilike(name))
# .map_to(User)
# .to_a
# end
# end
def [](attribute)
@relation[attribute]
rescue KeyError => exception
raise UnknownAttributeError.new(exception.message)
end
end
end
end
================================================
FILE: lib/hanami/model/mapping.rb
================================================
# frozen_string_literal: true
require "transproc/all"
module Hanami
module Model
# Mapping
#
# @since 0.1.0
# @api private
class Mapping
extend Transproc::Registry
import Transproc::HashTransformations
# @since 0.1.0
# @api private
def initialize(&blk)
@attributes = {}
@r_attributes = {}
instance_eval(&blk)
@processor = @attributes.empty? ? ::Hash : t(:rename_keys, @attributes)
end
# @api private
def t(name, *args)
self.class[name, *args]
end
# @api private
def model(entity)
end
# @api private
def register_as(name)
end
# @api private
def attribute(name, options)
from = options.fetch(:from, name)
@attributes[name] = from
@r_attributes[from] = name
end
# @api private
def process(input)
@processor[input]
end
# @api private
def reverse?
@r_attributes.any?
end
# @api private
def translate(attribute)
@r_attributes.fetch(attribute)
end
end
end
end
================================================
FILE: lib/hanami/model/migration.rb
================================================
# frozen_string_literal: true
module Hanami
module Model
# Database migration
#
# @since 0.7.0
# @api private
class Migration
# @since 0.7.0
# @api private
attr_reader :gateway
# @since 0.7.0
# @api private
attr_reader :migration
# @since 0.7.0
# @api private
def initialize(gateway, &block)
@gateway = gateway
@migration = gateway.migration(&block)
freeze
end
# @since 0.7.0
# @api private
def run(direction = :up)
migration.apply(gateway.connection, direction)
end
end
end
end
================================================
FILE: lib/hanami/model/migrator/adapter.rb
================================================
# frozen_string_literal: true
require "uri"
require "shellwords"
require "open3"
module Hanami
module Model
class Migrator
# Migrator base adapter
#
# @since 0.4.0
# @api private
class Adapter
# Migrations table to store migrations metadata.
#
# @since 0.4.0
# @api private
MIGRATIONS_TABLE = :schema_migrations
# Migrations table version column
#
# @since 0.4.0
# @api private
MIGRATIONS_TABLE_VERSION_COLUMN = :filename
# Loads and returns a specific adapter for the given connection.
#
# @since 0.4.0
# @api private
def self.for(configuration)
connection = Connection.new(configuration)
case connection.database_type
when :sqlite
require "hanami/model/migrator/sqlite_adapter"
SQLiteAdapter
when :postgres
require "hanami/model/migrator/postgres_adapter"
PostgresAdapter
when :mysql
require "hanami/model/migrator/mysql_adapter"
MySQLAdapter
else
self
end.new(connection)
end
# Initialize an adapter
#
# @since 0.4.0
# @api private
def initialize(connection)
@connection = connection
end
# Create database.
# It must be implemented by subclasses.
#
# @since 0.4.0
# @api private
#
# @see Hanami::Model::Migrator.create
def create
raise MigrationError.new("Current adapter (#{connection.database_type}) doesn't support create.")
end
# Drop database.
# It must be implemented by subclasses.
#
# @since 0.4.0
# @api private
#
# @see Hanami::Model::Migrator.drop
def drop
raise MigrationError.new("Current adapter (#{connection.database_type}) doesn't support drop.")
end
# @since 0.4.0
# @api private
def migrate(migrations, version)
version = Integer(version) unless version.nil?
Sequel::Migrator.run(connection.raw, migrations, target: version, allow_missing_migration_files: true)
rescue Sequel::Migrator::Error => exception
raise MigrationError.new(exception.message)
end
# @since 1.1.0
# @api private
def rollback(migrations, steps)
table = migrations_table_dataset
version = version_to_rollback(table, steps)
Sequel::Migrator.run(connection.raw, migrations, target: version, allow_missing_migration_files: true)
rescue Sequel::Migrator::Error => exception
raise MigrationError.new(exception.message)
end
# Load database schema.
# It must be implemented by subclasses.
#
# @since 0.4.0
# @api private
#
# @see Hanami::Model::Migrator.prepare
def load
raise MigrationError.new("Current adapter (#{connection.database_type}) doesn't support load.")
end
# Database version.
#
# @since 0.4.0
# @api private
def version
table = migrations_table_dataset
return if table.nil?
record = table.order(MIGRATIONS_TABLE_VERSION_COLUMN).last
return if record.nil?
record.fetch(MIGRATIONS_TABLE_VERSION_COLUMN).scan(MIGRATIONS_FILE_NAME_PATTERN).first.to_s
end
private
# @since 1.1.0
# @api private
MIGRATIONS_FILE_NAME_PATTERN = /\A[\d]{14}/.freeze
# @since 1.1.0
# @api private
def version_to_rollback(table, steps)
record = table.order(Sequel.desc(MIGRATIONS_TABLE_VERSION_COLUMN)).all[steps]
return 0 unless record
record.fetch(MIGRATIONS_TABLE_VERSION_COLUMN).scan(MIGRATIONS_FILE_NAME_PATTERN).first.to_i
end
# @since 1.1.0
# @api private
def migrations_table_dataset
connection.table(MIGRATIONS_TABLE)
end
# @since 0.5.0
# @api private
attr_reader :connection
# @since 0.4.0
# @api private
def schema
connection.schema
end
# Returns a database connection
#
# Given a DB connection URI we can connect to a specific database or not, we need this when creating
# or dropping a database. Important to notice that we can't always open a _global_ DB connection,
# because most of the times application's DB user has no rights to do so.
#
# @param global [Boolean] determine whether or not a connection should specify a database.
#
# @since 0.5.0
# @api private
def new_connection(global: false)
uri = global ? connection.global_uri : connection.uri
Sequel.connect(uri)
end
# @since 0.4.0
# @api private
def database
escape connection.database
end
# @since 0.4.0
# @api private
def port
escape connection.port
end
# @since 0.4.0
# @api private
def host
escape connection.host
end
# @since 0.4.0
# @api private
def username
escape connection.user
end
# @since 0.4.0
# @api private
def password
escape connection.password
end
# @since 0.4.0
# @api private
def migrations_table
escape MIGRATIONS_TABLE
end
# @since 0.4.0
# @api private
def escape(string)
Shellwords.escape(string) unless string.nil?
end
# @since 1.0.2
# @api private
def execute(command, env: {}, error: ->(err) { raise MigrationError.new(err) })
Open3.popen3(env, command) do |_, stdout, stderr, wait_thr|
error.call(stderr.read) unless wait_thr.value.success?
yield stdout if block_given?
end
end
end
end
end
end
================================================
FILE: lib/hanami/model/migrator/connection.rb
================================================
# frozen_string_literal: true
require "cgi"
module Hanami
module Model
class Migrator
# Sequel connection wrapper
#
# Normalize external adapters interfaces
#
# @since 0.5.0
# @api private
class Connection
# @since 0.5.0
# @api private
def initialize(configuration)
@configuration = configuration
end
# @since 0.7.0
# @api private
def raw
@raw ||= begin
Sequel.connect(
configuration.url,
loggers: [configuration.migrations_logger]
)
rescue Sequel::AdapterNotFound
raise MigrationError.new("Current adapter (#{configuration.adapter.type}) doesn't support SQL database operations.")
end
end
# Returns DB connection host
#
# Even when adapter doesn't provide it explicitly it tries to parse
#
# @since 0.5.0
# @api private
def host
@host ||= parsed_uri.host || parsed_opt("host")
end
# Returns DB connection port
#
# Even when adapter doesn't provide it explicitly it tries to parse
#
# @since 0.5.0
# @api private
def port
@port ||= parsed_uri.port || parsed_opt("port").to_i.nonzero?
end
# Returns DB name from conenction
#
# Even when adapter doesn't provide it explicitly it tries to parse
#
# @since 0.5.0
# @api private
def database
@database ||= parsed_uri.path[1..-1]
end
# Returns DB type
#
# @example
# connection.database_type
# # => 'postgres'
#
# @since 0.5.0
# @api private
def database_type
case uri
when /sqlite/
:sqlite
when /postgres/
:postgres
when /mysql/
:mysql
end
end
# Returns user from DB connection
#
# Even when adapter doesn't provide it explicitly it tries to parse
#
# @since 0.5.0
# @api private
def user
@user ||= parsed_opt("user") || parsed_uri.user
end
# Returns user from DB connection
#
# Even when adapter doesn't provide it explicitly it tries to parse
#
# @since 0.5.0
# @api private
def password
@password ||= parsed_opt("password") || parsed_uri.password
end
# Returns DB connection URI directly from adapter
#
# @since 0.5.0
# @api private
def uri
@configuration.url
end
# Returns DB connection wihout specifying database name
#
# @since 0.5.0
# @api private
def global_uri
uri.sub(parsed_uri.select(:path).first, "")
end
# Returns a boolean telling if a DB connection is from JDBC or not
#
# @since 0.5.0
# @api private
def jdbc?
!uri.scan("jdbc:").empty?
end
# Returns database connection URI instance without JDBC namespace
#
# @since 0.5.0
# @api private
def parsed_uri
@parsed_uri ||= URI.parse(uri.sub("jdbc:", ""))
end
# @api private
def schema
configuration.schema
end
# Return the database table for the given name
#
# @since 0.7.0
# @api private
def table(name)
raw[name] if raw.tables.include?(name)
end
private
# @since 1.0.0
# @api private
attr_reader :configuration
# Returns a value of a given query string param
#
# @param option [String] which option from database connection will be extracted from URI
#
# @since 0.5.0
# @api private
def parsed_opt(option, query: parsed_uri.query)
return if query.nil?
@parsed_query_opts ||= CGI.parse(query)
@parsed_query_opts[option].to_a.last
end
end
end
end
end
================================================
FILE: lib/hanami/model/migrator/logger.rb
================================================
# frozen_string_literal: true
require "hanami/logger"
module Hanami
module Model
class Migrator
# Automatic logger for migrations
#
# @since 1.0.0
# @api private
class Logger < Hanami::Logger
# Formatter for migrations logger
#
# @since 1.0.0
# @api private
class Formatter < Hanami::Logger::Formatter
private
# @since 1.0.0
# @api private
def _format(hash)
"[hanami] [#{hash.fetch(:severity)}] #{hash.fetch(:message)}\n"
end
end
# @since 1.0.0
# @api private
def initialize(stream)
super(nil, stream: stream, formatter: Formatter.new)
end
end
end
end
end
================================================
FILE: lib/hanami/model/migrator/mysql_adapter.rb
================================================
# frozen_string_literal: true
module Hanami
module Model
class Migrator
# MySQL adapter
#
# @since 0.4.0
# @api private
class MySQLAdapter < Adapter
# @since 0.7.0
# @api private
PASSWORD = "MYSQL_PWD"
# @since 1.3.3
# @api private
DEFAULT_PORT = 3306
# @since 1.0.0
# @api private
DB_CREATION_ERROR = "Database creation failed. If the database exists, " \
"then its console may be open. See this issue for more details: " \
"https://github.com/hanami/model/issues/250"
# @since 0.4.0
# @api private
def create
new_connection(global: true).run %(CREATE DATABASE `#{database}`;)
rescue Sequel::DatabaseError => exception
message = if exception.message.match(/database exists/)
DB_CREATION_ERROR
else
exception.message
end
raise MigrationError.new(message)
end
# @since 0.4.0
# @api private
def drop
new_connection(global: true).run %(DROP DATABASE `#{database}`;)
rescue Sequel::DatabaseError => exception
message = if exception.message.match(/doesn\'t exist/)
"Cannot find database: #{database}"
else
exception.message
end
raise MigrationError.new(message)
end
# @since 0.4.0
# @api private
def dump
dump_structure
dump_migrations_data
end
# @since 0.4.0
# @api private
def load
load_structure
end
private
# @since 0.7.0
# @api private
def password
connection.password
end
def port
super || DEFAULT_PORT
end
# @since 0.4.0
# @api private
def dump_structure
execute "mysqldump --host=#{host} --port=#{port} --user=#{username} --no-data --skip-comments --ignore-table=#{database}.#{migrations_table} #{database} > #{schema}", env: {PASSWORD => password}
end
# @since 0.4.0
# @api private
def load_structure
execute("mysql --host=#{host} --port=#{port} --user=#{username} #{database} < #{escape(schema)}", env: {PASSWORD => password}) if schema.exist?
end
# @since 0.4.0
# @api private
def dump_migrations_data
execute "mysqldump --host=#{host} --port=#{port} --user=#{username} --skip-comments #{database} #{migrations_table} >> #{schema}", env: {PASSWORD => password}
end
end
end
end
end
================================================
FILE: lib/hanami/model/migrator/postgres_adapter.rb
================================================
# frozen_string_literal: true
require "hanami/utils/blank"
module Hanami
module Model
class Migrator
# PostgreSQL adapter
#
# @since 0.4.0
# @api private
class PostgresAdapter < Adapter
# @since 0.4.0
# @api private
HOST = "PGHOST"
# @since 0.4.0
# @api private
PORT = "PGPORT"
# @since 0.4.0
# @api private
USER = "PGUSER"
# @since 0.4.0
# @api private
PASSWORD = "PGPASSWORD"
# @since 1.0.0
# @api private
DB_CREATION_ERROR = "createdb: database creation failed. If the database exists, " \
"then its console may be open. See this issue for more details: " \
"https://github.com/hanami/model/issues/250"
# @since 0.4.0
# @api private
def create
call_db_command("createdb")
end
# @since 0.4.0
# @api private
def drop
call_db_command("dropdb")
end
# @since 0.4.0
# @api private
def dump
dump_structure
dump_migrations_data
end
# @since 0.4.0
# @api private
def load
load_structure
end
private
# @since 1.3.3
# @api private
def environment_variables
{}.tap do |env|
env[HOST] = host unless host.nil?
env[PORT] = port.to_s unless port.nil?
env[PASSWORD] = password unless password.nil?
env[USER] = username unless username.nil?
end
end
# @since 0.4.0
# @api private
def dump_structure
execute "pg_dump -s -x -O -T #{migrations_table} -f #{escape(schema)} #{database}", env: environment_variables
end
# @since 0.4.0
# @api private
def load_structure
return unless schema.exist?
execute "psql -X -q -f #{escape(schema)} #{database}", env: environment_variables
end
# @since 0.4.0
# @api private
def dump_migrations_data
error = ->(err) { raise MigrationError.new(err) unless err =~ /no matching tables/i }
execute "pg_dump -t #{migrations_table} #{database} >> #{escape(schema)}", error: error, env: environment_variables
end
# @since 0.5.1
# @api private
def call_db_command(command)
require "open3"
begin
Open3.popen3(environment_variables, command, database) do |_stdin, _stdout, stderr, wait_thr|
raise MigrationError.new(modified_message(stderr.read)) unless wait_thr.value.success? # wait_thr.value is the exit status
end
rescue SystemCallError => exception
raise MigrationError.new(modified_message(exception.message))
end
end
# @since 1.1.0
# @api private
def modified_message(original_message)
case original_message
when /already exists/
DB_CREATION_ERROR
when /does not exist/
"Cannot find database: #{database}"
when /No such file or directory/
"Could not find executable in your PATH: `#{original_message.split.last}`"
else
original_message
end
end
end
end
end
end
================================================
FILE: lib/hanami/model/migrator/sqlite_adapter.rb
================================================
# frozen_string_literal: true
require "pathname"
require "hanami/utils"
require "English"
module Hanami
module Model
class Migrator
# SQLite3 Migrator
#
# @since 0.4.0
# @api private
class SQLiteAdapter < Adapter
# No-op for in-memory databases
#
# @since 0.4.0
# @api private
module Memory
# @since 0.4.0
# @api private
def create
end
# @since 0.4.0
# @api private
def drop
end
end
# Initialize adapter
#
# @since 0.4.0
# @api private
def initialize(configuration)
super
extend Memory if memory?
end
# @since 0.4.0
# @api private
def create
path.dirname.mkpath
FileUtils.touch(path)
rescue Errno::EACCES, Errno::EPERM
raise MigrationError.new("Permission denied: #{path.sub(/\A\/\//, '')}")
end
# @since 0.4.0
# @api private
def drop
path.delete
rescue Errno::ENOENT
raise MigrationError.new("Cannot find database: #{path.sub(/\A\/\//, '')}")
end
# @since 0.4.0
# @api private
def dump
dump_structure
dump_migrations_data
end
# @since 0.4.0
# @api private
def load
load_structure
end
private
# @since 0.4.0
# @api private
def path
root.join(
@connection.uri.sub(/\A(jdbc:sqlite:\/\/|sqlite:\/\/)/, "")
)
end
# @since 0.4.0
# @api private
def root
Hanami::Model.configuration.root
end
# @since 0.4.0
# @api private
def memory?
uri = path.to_s
uri.match(/sqlite\:\/\z/) ||
uri.match(/\:memory\:/)
end
# @since 0.4.0
# @api private
def dump_structure
execute "sqlite3 #{escape(path)} .schema > #{escape(schema)}"
end
# @since 0.4.0
# @api private
def load_structure
execute "sqlite3 #{escape(path)} < #{escape(schema)}" if schema.exist?
end
# @since 0.4.0
# @api private
#
def dump_migrations_data
execute "sqlite3 #{escape(path)} .dump" do |stdout|
begin
contents = stdout.read.split($INPUT_RECORD_SEPARATOR)
contents = contents.grep(/^INSERT INTO "?#{migrations_table}"?/)
::File.open(schema, ::File::CREAT | ::File::BINARY | ::File::WRONLY | ::File::APPEND) do |file|
file.write(contents.join($INPUT_RECORD_SEPARATOR))
end
rescue => exception
raise MigrationError.new(exception.message)
end
end
end
end
end
end
end
================================================
FILE: lib/hanami/model/migrator.rb
================================================
# frozen_string_literal: true
require "sequel"
require "sequel/extensions/migration"
module Hanami
module Model
# Migration error
#
# @since 0.4.0
class MigrationError < Hanami::Model::Error
end
# Database schema migrator
#
# @since 0.4.0
class Migrator
require "hanami/model/migrator/connection"
require "hanami/model/migrator/adapter"
# Create database defined by current configuration.
#
# It's only implemented for the following databases:
#
# * SQLite3
# * PostgreSQL
# * MySQL
#
# @raise [Hanami::Model::MigrationError] if an error occurs
#
# @since 0.4.0
#
# @see Hanami::Model::Configuration#adapter
#
# @example
# require 'hanami/model'
# require 'hanami/model/migrator'
#
# Hanami::Model.configure do
# # ...
# adapter :sql, 'postgres://localhost/foo'
# end
#
# Hanami::Model::Migrator.create # Creates `foo' database
#
# NOTE: Class level interface SHOULD be removed in Hanami 2.0
def self.create
new.create
end
# Drop database defined by current configuration.
#
# It's only implemented for the following databases:
#
# * SQLite3
# * PostgreSQL
# * MySQL
#
# @raise [Hanami::Model::MigrationError] if an error occurs
#
# @since 0.4.0
#
# @see Hanami::Model::Configuration#adapter
#
# @example
# require 'hanami/model'
# require 'hanami/model/migrator'
#
# Hanami::Model.configure do
# # ...
# adapter :sql, 'postgres://localhost/foo'
# end
#
# Hanami::Model::Migrator.drop # Drops `foo' database
#
# NOTE: Class level interface SHOULD be removed in Hanami 2.0
def self.drop
new.drop
end
# Migrate database schema
#
# It's possible to migrate "down" by specifying a version
# (eg. <tt>"20150610133853"</tt>)
#
# @param version [String,NilClass] target version
#
# @raise [Hanami::Model::MigrationError] if an error occurs
#
# @since 0.4.0
#
# @see Hanami::Model::Configuration#adapter
# @see Hanami::Model::Configuration#migrations
# @see Hanami::Model::Configuration#rollback
#
# @example Migrate Up
# require 'hanami/model'
# require 'hanami/model/migrator'
#
# Hanami::Model.configure do
# # ...
# adapter :sql, 'postgres://localhost/foo'
# migrations 'db/migrations'
# end
#
# # Reads all files from "db/migrations" and apply them
# Hanami::Model::Migrator.migrate
#
# @example Migrate Down
# require 'hanami/model'
# require 'hanami/model/migrator'
#
# Hanami::Model.configure do
# # ...
# adapter :sql, 'postgres://localhost/foo'
# migrations 'db/migrations'
# end
#
# # Reads all files from "db/migrations" and apply them
# Hanami::Model::Migrator.migrate
#
# # Migrate to a specific version
# Hanami::Model::Migrator.migrate(version: "20150610133853")
#
# NOTE: Class level interface SHOULD be removed in Hanami 2.0
def self.migrate(version: nil)
new.migrate(version: version)
end
# Rollback database schema
#
# @param steps [Number,NilClass] number of versions to rollback
#
# @raise [Hanami::Model::MigrationError] if an error occurs
#
# @since 1.1.0
#
# @see Hanami::Model::Configuration#adapter
# @see Hanami::Model::Configuration#migrations
# @see Hanami::Model::Configuration#migrate
#
# @example Rollback
# require 'hanami/model'
# require 'hanami/model/migrator'
#
# Hanami::Model.configure do
# # ...
# adapter :sql, 'postgres://localhost/foo'
# migrations 'db/migrations'
# end
#
# # Reads all files from "db/migrations" and apply them
# Hanami::Model::Migrator.migrate
#
# # By default only rollback one version
# Hanami::Model::Migrator.rollback
#
# # Use a hash passing a number of versions to rollback, it will rollbacks those versions
# Hanami::Model::Migrator.rollback(versions: 2)
#
# NOTE: Class level interface SHOULD be removed in Hanami 2.0
def self.rollback(steps: 1)
new.rollback(steps: steps)
end
# Migrate, dump schema, delete migrations.
#
# This is an experimental feature.
# It may change or be removed in the future.
#
# Actively developed applications accumulate tons of migrations.
# In the long term they are hard to maintain and slow to execute.
#
# "Apply" feature solves this problem.
#
# It keeps an updated SQL file with the structure of the database.
# This file can be used to create fresh databases for developer machines
# or during testing. This is faster than to run dozen or hundred migrations.
#
# When we use "apply", it eliminates all the migrations that are no longer
# necessary.
#
# @raise [Hanami::Model::MigrationError] if an error occurs
#
# @since 0.4.0
#
# @see Hanami::Model::Configuration#adapter
# @see Hanami::Model::Configuration#migrations
#
# @example Apply Migrations
# require 'hanami/model'
# require 'hanami/model/migrator'
#
# Hanami::Model.configure do
# # ...
# adapter :sql, 'postgres://localhost/foo'
# migrations 'db/migrations'
# schema 'db/schema.sql'
# end
#
# # Reads all files from "db/migrations" and apply and delete them.
# # It generates an updated version of "db/schema.sql"
# Hanami::Model::Migrator.apply
#
# NOTE: Class level interface SHOULD be removed in Hanami 2.0
def self.apply
new.apply
end
# Prepare database: drop, create, load schema (if any), migrate.
#
# This is designed for development machines and testing mode.
# It works faster if used with <tt>apply</tt>.
#
# @raise [Hanami::Model::MigrationError] if an error occurs
#
# @since 0.4.0
#
# @see Hanami::Model::Migrator.apply
#
# @example Prepare Database
# require 'hanami/model'
# require 'hanami/model/migrator'
#
# Hanami::Model.configure do
# # ...
# adapter :sql, 'postgres://localhost/foo'
# migrations 'db/migrations'
# end
#
# Hanami::Model::Migrator.prepare # => creates `foo' and runs migrations
#
# @example Prepare Database (with schema dump)
# require 'hanami/model'
# require 'hanami/model/migrator'
#
# Hanami::Model.configure do
# # ...
# adapter :sql, 'postgres://localhost/foo'
# migrations 'db/migrations'
# schema 'db/schema.sql'
# end
#
# Hanami::Model::Migrator.apply # => updates schema dump
# Hanami::Model::Migrator.prepare # => creates `foo', load schema and run pending migrations (if any)
#
# NOTE: Class level interface SHOULD be removed in Hanami 2.0
def self.prepare
new.prepare
end
# Return current database version timestamp
#
# If no migrations were ran, it returns <tt>nil</tt>.
#
# @return [String,NilClass] current version, if previously migrated
#
# @since 0.4.0
#
# @example
# # Given last migrations is:
# # 20150610133853_create_books.rb
#
# Hanami::Model::Migrator.version # => "20150610133853"
#
# NOTE: Class level interface SHOULD be removed in Hanami 2.0
def self.version
new.version
end
# Instantiate a new migrator
#
# @param configuration [Hanami::Model::Configuration] framework configuration
#
# @return [Hanami::Model::Migrator] a new instance
#
# @since 0.7.0
# @api private
def initialize(configuration: self.class.configuration)
@configuration = configuration
@adapter = Adapter.for(configuration)
end
# @since 0.7.0
# @api private
#
# @see Hanami::Model::Migrator.create
def create
adapter.create
end
# @since 0.7.0
# @api private
#
# @see Hanami::Model::Migrator.drop
def drop
adapter.drop
end
# @since 0.7.0
# @api private
#
# @see Hanami::Model::Migrator.migrate
def migrate(version: nil)
adapter.migrate(migrations, version) if migrations?
end
# @since 1.1.0
# @api private
#
# @see Hanami::Model::Migrator.rollback
def rollback(steps: 1)
adapter.rollback(migrations, steps.abs) if migrations?
end
# @since 0.7.0
# @api private
#
# @see Hanami::Model::Migrator.apply
def apply
migrate
adapter.dump
delete_migrations
end
# @since 0.7.0
# @api private
#
# @see Hanami::Model::Migrator.prepare
def prepare
drop
rescue # rubocop:disable Lint/SuppressedException
ensure
create
adapter.load
migrate
end
# @since 0.7.0
# @api private
#
# @see Hanami::Model::Migrator.version
def version
adapter.version
end
# Hanami::Model configuration
#
# @since 0.4.0
# @api private
def self.configuration
Model.configuration
end
private
# @since 0.7.0
# @api private
attr_reader :configuration
# @since 0.7.0
# @api private
attr_reader :connection
# @since 0.7.0
# @api private
attr_reader :adapter
# Migrations directory
#
# @since 0.7.0
# @api private
def migrations
configuration.migrations
end
# Check if there are migrations
#
# @since 0.7.0
# @api private
def migrations?
Dir["#{migrations}/*.rb"].any?
end
# Delete all the migrations
#
# @since 0.7.0
# @api private
def delete_migrations
migrations.each_child(&:delete)
end
end
end
end
================================================
FILE: lib/hanami/model/plugins/mapping.rb
================================================
# frozen_string_literal: true
module Hanami
module Model
module Plugins
# Transform output into model domain types (entities).
#
# @since 0.7.0
# @api private
module Mapping
# Takes the output and applies the transformations
#
# @since 0.7.0
# @api private
class InputWithMapping < WrappingInput
# @since 0.7.0
# @api private
def initialize(relation, input)
super
@mapping = Hanami::Model.configuration.mappings[relation.name.to_sym]
end
# Processes the output
#
# @since 0.7.0
# @api private
def [](value)
@input[@mapping.process(value)]
end
end
# Class interface
#
# @since 0.7.0
# @api private
module ClassMethods
# Builds the output processor
#
# @since 0.7.0
# @api private
def build(relation, options = {})
wrapped_input = InputWithMapping.new(relation, options.fetch(:input) { input })
super(relation, options.merge(input: wrapped_input))
end
end
# @since 0.7.0
# @api private
def self.included(klass)
super
klass.extend ClassMethods
end
end
end
end
end
================================================
FILE: lib/hanami/model/plugins/schema.rb
================================================
# frozen_string_literal: true
module Hanami
module Model
module Plugins
# Transform input values into database specific types (primitives).
#
# @since 0.7.0
# @api private
module Schema
# Takes the input and applies the values transformations.
#
# @since 0.7.0
# @api private
class InputWithSchema < WrappingInput
# @since 0.7.0
# @api private
def initialize(relation, input)
super
@schema = relation.input_schema
end
# Processes the input
#
# @since 0.7.0
# @api private
def [](value)
@schema[@input[value]]
end
end
# Class interface
#
# @since 0.7.0
# @api private
module ClassMethods
# Builds the input processor
#
# @since 0.7.0
# @api private
def build(relation, options = {})
wrapped_input = InputWithSchema.new(relation, options.fetch(:input) { input })
super(relation, options.merge(input: wrapped_input))
end
end
# @since 0.7.0
# @api private
def self.included(klass)
super
klass.extend ClassMethods
end
end
end
end
end
================================================
FILE: lib/hanami/model/plugins/timestamps.rb
================================================
# frozen_string_literal: true
module Hanami
module Model
module Plugins
# Automatically set/update timestamp columns for create/update commands
#
# @since 0.7.0
# @api private
module Timestamps
# Takes the input and applies the timestamp transformation.
# This is an "abstract class", please look at the subclasses for
# specific behaviors.
#
# @since 0.7.0
# @api private
class InputWithTimestamp < WrappingInput
# Conventional timestamp names
#
# @since 0.7.0
# @api private
TIMESTAMPS = %i[created_at updated_at].freeze
# @since 0.7.0
# @api private
def initialize(relation, input)
super
@timestamps = relation.columns & TIMESTAMPS
end
# Processes the input
#
# @since 0.7.0
# @api private
def [](value)
return @input[value] unless timestamps?
_touch(@input[value], Time.now)
end
protected
# @since 0.7.0
# @api private
def _touch(_value)
raise NoMethodError
end
private
# @since 0.7.0
# @api private
def timestamps?
!@timestamps.empty?
end
end
# Updates <tt>updated_at</tt> timestamp for update commands
#
# @since 0.7.0
# @api private
class InputWithUpdateTimestamp < InputWithTimestamp
protected
# @since 0.7.0
# @api private
def _touch(value, now)
value[:updated_at] ||= now if @timestamps.include?(:updated_at)
value
end
end
# Sets <tt>created_at</tt> and <tt>updated_at</tt> timestamps for create commands
#
# @since 0.7.0
# @api private
class InputWithCreateTimestamp < InputWithUpdateTimestamp
protected
# @since 0.7.0
# @api private
def _touch(value, now)
super
value[:created_at] ||= now if @timestamps.include?(:created_at)
value
end
end
# Class interface
#
# @since 0.7.0
# @api private
module ClassMethods
# Build an input processor according to the current command (create or update).
#
# @since 0.7.0
# @api private
def build(relation, options = {})
plugin = if self < ROM::Commands::Create
InputWithCreateTimestamp
else
InputWithUpdateTimestamp
end
wrapped_input = plugin.new(relation, options.fetch(:input) { input })
super(relation, options.merge(input: wrapped_input))
end
end
# @since 0.7.0
# @api private
def self.included(klass)
super
klass.extend ClassMethods
end
end
end
end
end
================================================
FILE: lib/hanami/model/plugins.rb
================================================
# frozen_string_literal: true
module Hanami
module Model
# Plugins to extend read/write operations from/to the database
#
# @since 0.7.0
# @api private
module Plugins
# Wrapping input
#
# @since 0.7.0
# @api private
class WrappingInput
# @since 0.7.0
# @api private
def initialize(_relation, input)
@input = input || Hash
end
end
require "hanami/model/plugins/mapping"
require "hanami/model/plugins/schema"
require "hanami/model/plugins/timestamps"
end
end
end
================================================
FILE: lib/hanami/model/relation_name.rb
================================================
# frozen_string_literal: true
require_relative "entity_name"
require "hanami/utils/string"
module Hanami
module Model
# Conventional name for relations.
#
# Given a repository named <tt>SourceFileRepository</tt>, the associated
# relation will be <tt>:source_files</tt>.
#
# @since 0.7.0
# @api private
class RelationName < EntityName
# @param name [Class,String] the class or its name
# @return [String] the relation name
#
# @since 0.7.0
# @api private
def self.new(name)
Utils::String.transform(super, :underscore, :pluralize)
end
end
end
end
================================================
FILE: lib/hanami/model/sql/console.rb
================================================
# frozen_string_literal: true
require "uri"
module Hanami
module Model
module Sql
# SQL console
#
# @since 0.7.0
# @api private
class Console
extend Forwardable
# @since 0.7.0
# @api private
def_delegator :console, :connection_string
# @since 0.7.0
# @api private
def initialize(uri)
@uri = URI.parse(uri)
end
private
# @since 0.7.0
# @api private
def console
case @uri.scheme
when "sqlite"
require "hanami/model/sql/consoles/sqlite"
Sql::Consoles::Sqlite.new(@uri)
when "postgres", "postgresql"
require "hanami/model/sql/consoles/postgresql"
Sql::Consoles::Postgresql.new(@uri)
when "mysql", "mysql2"
require "hanami/model/sql/consoles/mysql"
Sql::Consoles::Mysql.new(@uri)
end
end
end
end
end
end
================================================
FILE: lib/hanami/model/sql/consoles/abstract.rb
================================================
# frozen_string_literal: true
module Hanami
module Model
module Sql
module Consoles
# Abstract adapter
#
# @since 0.7.0
# @api private
class Abstract
# @since 0.7.0
# @api private
def initialize(uri)
@uri = uri
end
private
# @since 0.7.0
# @api private
def database_name
@uri.path.sub(/^\//, "")
end
# @since 0.7.0
# @api private
def concat(*tokens)
tokens.join
end
end
end
end
end
end
================================================
FILE: lib/hanami/model/sql/consoles/mysql.rb
================================================
# frozen_string_literal: true
require_relative "abstract"
module Hanami
module Model
module Sql
module Consoles
# MySQL adapter
#
# @since 0.7.0
# @api private
class Mysql < Abstract
# @since 0.7.0
# @api private
COMMAND = "mysql"
# @since 0.7.0
# @api private
def connection_string
concat(command, host, database, port, username, password)
end
private
# @since 0.7.0
# @api private
def command
COMMAND
end
# @since 0.7.0
# @api private
def host
" -h #{@uri.host}"
end
# @since 0.7.0
# @api private
def database
" -D #{database_name}"
end
# @since 0.7.0
# @api private
def port
" -P #{@uri.port}" unless @uri.port.nil?
end
# @since 0.7.0
# @api private
def username
" -u #{@uri.user}" unless @uri.user.nil?
end
# @since 0.7.0
# @api private
def password
" -p #{@uri.password}" unless @uri.password.nil?
end
end
end
end
end
end
================================================
FILE: lib/hanami/model/sql/consoles/postgresql.rb
================================================
# frozen_string_literal: true
require_relative "abstract"
require "cgi"
module Hanami
module Model
module Sql
module Consoles
# PostgreSQL adapter
#
# @since 0.7.0
# @api private
class Postgresql < Abstract
# @since 0.7.0
# @api private
COMMAND = "psql"
# @since 0.7.0
# @api private
PASSWORD = "PGPASSWORD"
# @since 0.7.0
# @api private
def connection_string
configure_password
concat(command, host, database, port, username)
end
private
# @since 0.7.0
# @api private
def command
COMMAND
end
# @since 0.7.0
# @api private
def host
" -h #{query['host'] || @uri.host}"
end
# @since 0.7.0
# @api private
def database
" -d #{database_name}"
end
# @since 0.7.0
# @api private
def port
port = query["port"] || @uri.port
" -p #{port}" if port
end
# @since 0.7.0
# @api private
def username
username = query["user"] || @uri.user
" -U #{username}" if username
end
# @since 0.7.0
# @api private
def configure_password
password = query["password"] || @uri.password
ENV[PASSWORD] = CGI.unescape(query["password"] || @uri.password) if password
end
# @since 1.1.0
# @api private
def query
return {} if @uri.query.nil? || @uri.query.empty?
parsed_query = @uri.query.split("&").map { |a| a.split("=") }
@query ||= Hash[parsed_query]
end
end
end
end
end
end
================================================
FILE: lib/hanami/model/sql/consoles/sqlite.rb
================================================
# frozen_string_literal: true
require_relative "abstract"
require "shellwords"
module Hanami
module Model
module Sql
module Consoles
# SQLite adapter
#
# @since 0.7.0
# @api private
class Sqlite < Abstract
# @since 0.7.0
# @api private
COMMAND = "sqlite3"
# @since 0.7.0
# @api private
def connection_string
concat(command, " ", host, database)
end
private
# @since 0.7.0
# @api private
def command
COMMAND
end
# @since 0.7.0
# @api private
def host
@uri.host unless @uri.host.nil?
end
# @since 0.7.0
# @api private
def database
Shellwords.escape(@uri.path)
end
end
end
end
end
end
================================================
FILE: lib/hanami/model/sql/entity/schema.rb
================================================
# frozen_string_literal: true
require "hanami/entity/schema"
require "hanami/model/types"
require "hanami/model/association"
module Hanami
module Model
module Sql
module Entity
# SQL Entity schema
#
# This schema setup is automatic.
#
# Hanami looks at the database columns, associations and potentially to
# the mapping in the repository (optional, only for legacy databases).
#
# @since 0.7.0
# @api private
#
# @see Hanami::Entity::Schema
class Schema < Hanami::Entity::Schema
# Build a new instance of Schema according to database columns,
# associations and potentially to mapping defined by the repository.
#
# @param registry [Hash] a registry that keeps reference between
# entities class and their underscored names
# @param relation [ROM::Relation] the database relation
# @param mapping [Hanami::Model::Mapping] the optional repository
# mapping
#
# @return [Hanami::Model::Sql::Entity::Schema] the schema
#
# @since 0.7.0
# @api private
def initialize(registry, relation, mapping)
attributes = build(registry, relation, mapping)
@schema = Types::Coercible::Hash.schema(attributes)
@attributes = Hash[attributes.map { |k, _| [k, true] }]
freeze
end
# Process attributes
#
# @param attributes [#to_hash] the attributes hash
#
# @raise [TypeError] if the process fails
#
# @since 1.0.1
# @api private
def call(attributes)
schema.call(attributes)
end
# @since 1.0.1
# @api private
alias_method :[], :call
# Check if the attribute is known
#
# @param name [Symbol] the attribute name
#
# @return [TrueClass,FalseClass] the result of the check
#
# @since 0.7.0
# @api private
def attribute?(name)
attributes.key?(name)
end
private
# @since 0.7.0
# @api private
attr_reader :attributes
# Build the schema
#
# @param registry [Hash] a registry that keeps reference between
# entities class and their underscored names
# @param relation [ROM::Relation] the database relation
# @param mapping [Hanami::Model::Mapping] the optional repository
# mapping
#
# @return [Dry::Types::Constructor] the inner schema
#
# @since 0.7.0
# @api private
def build(registry, relation, mapping)
build_attributes(relation, mapping).merge(
build_associations(registry, relation.associations)
)
end
# Extract a set of attributes from the database table or from the
# optional repository mapping.
#
# @param relation [ROM::Relation] the database relation
# @param mapping [Hanami::Model::Mapping] the optional repository
# mapping
#
# @return [Hash] a set of attributes
#
# @since 0.7.0
# @api private
def build_attributes(relation, mapping)
schema = relation.schema.to_h
schema.each_with_object({}) do |(attribute, type), result|
attribute = mapping.translate(attribute) if mapping.reverse?
result[attribute] = coercible(type)
end
end
# Merge attributes and associations
#
# @param registry [Hash] a registry that keeps reference between
# entities class and their underscored names
# @param associations [ROM::AssociationSet] a set of associations for
# the current relation
#
# @return [Hash] attributes with associations
#
# @since 0.7.0
# @api private
def build_associations(registry, associations)
associations.each_with_object({}) do |(name, association), result|
target = registry.fetch(association.target.to_sym)
result[name] = Association.lookup(association).schema_type(target)
end
end
# Converts given ROM type into coercible type for entity attribute
#
# @since 0.7.0
# @api private
def coercible(type)
Types::Schema.coercible(type)
end
end
end
end
end
end
================================================
FILE: lib/hanami/model/sql/types/schema/coercions.rb
================================================
# frozen_string_literal: true
require "hanami/utils/string"
require "hanami/utils/hash"
module Hanami
module Model
module Sql
module Types
module Schema
# Coercions for schema types
#
# @since 0.7.0
# @api private
#
# rubocop:disable Metrics/ModuleLength
module Coercions
# Coerces given argument into Integer
#
# @param arg [#to_i,#to_int] the argument to coerce
#
# @return [Integer] the result of the coercion
#
# @raise [ArgumentError] if the coercion fails
#
# @since 0.7.0
# @api private
def self.int(arg)
case arg
when ::Integer
arg
when ::Float, ::BigDecimal, ::String, ::Hanami::Utils::String, ->(a) { a.respond_to?(:to_int) }
::Kernel.Integer(arg)
else
raise ArgumentError.new("invalid value for Integer(): #{arg.inspect}")
end
end
# Coerces given argument into Float
#
# @param arg [#to_f] the argument to coerce
#
# @return [Float] the result of the coercion
#
# @raise [ArgumentError] if the coercion fails
#
# @since 0.7.0
# @api private
def self.float(arg)
case arg
when ::Float
arg
when ::Integer, ::BigDecimal, ::String, ::Hanami::Utils::String, ->(a) { a.respond_to?(:to_f) && !a.is_a?(::Time) }
::Kernel.Float(arg)
else
raise ArgumentError.new("invalid value for Float(): #{arg.inspect}")
end
end
# Coerces given argument into BigDecimal
#
# @param arg [#to_d] the argument to coerce
#
# @return [BigDecimal] the result of the coercion
#
# @raise [ArgumentError] if the coercion fails
#
# @since 0.7.0
# @api private
def self.decimal(arg)
case arg
when ::BigDecimal
arg
when ::Integer, ::Float, ::String, ::Hanami::Utils::String
::Kernel.BigDecimal(arg, ::Float::DIG)
when ->(a) { a.respond_to?(:to_d) }
arg.to_d
else
raise ArgumentError.new("invalid value for BigDecimal(): #{arg.inspect}")
end
end
# Coerces given argument into Date
#
# @param arg [#to_date,String] the argument to coerce
#
# @return [Date] the result of the coercion
#
# @raise [ArgumentError] if the coercion fails
#
# @since 0.7.0
# @api private
def self.date(arg)
case arg
when ::Date
arg
when ::String, ::Hanami::Utils::String
::Date.parse(arg)
when ::Time, ::DateTime, ->(a) { a.respond_to?(:to_date) }
arg.to_date
else
raise ArgumentError.new("invalid value for Date(): #{arg.inspect}")
end
end
# Coerces given argument into DateTime
#
# @param arg [#to_datetime,String] the argument to coerce
#
# @return [DateTime] the result of the coercion
#
# @raise [ArgumentError] if the coercion fails
#
# @since 0.7.0
# @api private
def self.datetime(arg)
case arg
when ::DateTime
arg
when ::String, ::Hanami::Utils::String
::DateTime.parse(arg)
when ::Date, ::Time, ->(a) { a.respond_to?(:to_datetime) }
arg.to_datetime
else
raise ArgumentError.new("invalid value for DateTime(): #{arg.inspect}")
end
end
# Coerces given argument into Time
#
# @param arg [#to_time,String] the argument to coerce
#
# @return [Time] the result of the coercion
#
# @raise [ArgumentError] if the coercion fails
#
# @since 0.7.0
# @api private
def self.time(arg)
case arg
when ::Time
arg
when ::String, ::Hanami::Utils::String
::Time.parse(arg)
when ::Date, ::DateTime, ->(a) { a.respond_to?(:to_time) }
arg.to_time
when ::Integer
::Time.at(arg)
else
raise ArgumentError.new("invalid value for Time(): #{arg.inspect}")
end
end
# Coerces given argument into Array
#
# @param arg [#to_ary] the argument to coerce
#
# @return [Array] the result of the coercion
#
# @raise [ArgumentError] if the coercion fails
#
# @since 0.7.0
# @api private
def self.array(arg)
case arg
when ::Array
arg
when ->(a) { a.respond_to?(:to_ary) }
::Kernel.Array(arg)
else
raise ArgumentError.new("invalid value for Array(): #{arg.inspect}")
end
end
# Coerces given argument into Hash
#
# @param arg [#to_hash] the argument to coerce
#
# @return [Hash] the result of the coercion
#
# @raise [ArgumentError] if the coercion fails
#
# @since 0.7.0
# @api private
def self.hash(arg)
case arg
when ::Hash
arg
when ->(a) { a.respond_to?(:to_hash) }
Utils::Hash.deep_symbolize(
::Kernel.Hash(arg)
)
else
raise ArgumentError.new("invalid value for Hash(): #{arg.inspect}")
end
end
# Coerces given argument to appropriate Postgres JSON(B) type, i.e. Hash or Array
#
# @param arg [Object] the object to coerce
#
# @return [Hash, Array] the result of the coercion
#
# @raise [ArgumentError] if the coercion fails
#
# @since 1.0.2
# @api private
def self.pg_json(arg)
case arg
when ->(a) { a.respond_to?(:to_hash) }
hash(arg)
when ->(a) { a.respond_to?(:to_a) }
array(arg)
else
raise ArgumentError.new("invalid value for PG_JSON(): #{arg.inspect}")
end
end
end
# rubocop:enable Metrics/ModuleLength
end
end
end
end
end
================================================
FILE: lib/hanami/model/sql/types.rb
================================================
# frozen_string_literal: true
require "hanami/model/types"
require "rom/types"
module Hanami
module Model
module Sql
# Types definitions for SQL databases
#
# @since 0.7.0
module Types
include Dry::Types.module
# Types for schema definitions
#
# @since 0.7.0
module Schema
require "hanami/model/sql/types/schema/coercions"
String = Types::Optional::Coercible::String
Int = Types::Strict::Nil | Types::Int.constructor(Coercions.method(:int))
Float = Types::Strict::Nil | Types::Float.constructor(Coercions.method(:float))
Decimal = Types::Strict::Nil | Types::Float.constructor(Coercions.method(:decimal))
Bool = Types::Strict::Nil | Types::Strict::Bool
Date = Types::Strict::Nil | Types::Date.constructor(Coercions.method(:date))
DateTime = Types::Strict::Nil | Types::DateTime.constructor(Coercions.method(:datetime))
Time = Types::Strict::Nil | Types::Time.constructor(Coercions.method(:time))
Array = Types::Strict::Nil | Types::Array.constructor(Coercions.method(:array))
Hash = Types::Strict::Nil | Types::Hash.constructor(Coercions.method(:hash))
PG_JSON = Types::Strict::Nil | Types::Any.constructor(Coercions.method(:pg_json))
# @since 0.7.0
# @api private
MAPPING = {
Types::String.pristine => Schema::String,
Types::Int.pristine => Schema::Int,
Types::Float.pristine => Schema::Float,
Types::Decimal.pristine => Schema::Decimal,
Types::Bool.pristine => Schema::Bool,
Types::Date.pristine => Schema::Date,
Types::DateTime.pristine => Schema::DateTime,
Types::Time.pristine => Schema::Time,
Types::Array.pristine => Schema::Array,
Types::Hash.pristine => Schema::Hash,
Types::String.optional.pristine => Schema::String,
Types::Int.optional.pristine => Schema::Int,
Types::Float.optional.pristine => Schema::Float,
Types::Decimal.optional.pristine => Schema::Decimal,
Types::Bool.optional.pristine => Schema::Bool,
Types::Date.optional.pristine => Schema::Date,
Types::DateTime.optional.pristine => Schema::DateTime,
Types::Time.optional.pristine => Schema::Time,
Types::Array.optional.pristine => Schema::Array,
Types::Hash.optional.pristine => Schema::Hash
}.freeze
# Convert given type into coercible
#
# @since 0.7.0
# @api private
def self.coercible(attribute)
return attribute if attribute.constrained?
type = attribute.type
unwrapped = type.optional? ? type.right : type
# NOTE: In the future rom-sql should be able to always return Ruby
# types instead of Sequel types. When that will happen we can get
# rid of this logic in the block and fall back to:
#
# MAPPING.fetch(unwrapped.pristine, attribute)
MAPPING.fetch(unwrapped.pristine) do
if pg_json?(unwrapped.pristine)
Schema::PG_JSON
else
attribute
end
end
end
# @since 1.0.4
# @api private
def self.pg_json_pristines
@pg_json_pristines ||= ::Hash.new do |hash, type|
hash[type] = (ROM::SQL::Types::PG.const_get(type).pristine if defined?(ROM::SQL::Types::PG))
end
end
# @since 1.0.2
# @api private
def self.pg_json?(pristine)
pristine == pg_json_pristines["JSONB"] || # rubocop:disable Style/MultipleComparison
pristine == pg_json_pristines["JSON"]
end
private_class_method :pg_json?
# Coercer for SQL associations target
#
# @since 0.7.0
# @api private
class AssociationType < Hanami::Model::Types::Schema::CoercibleType
# Check if value can be coerced
#
# @param value [Object] the value
#
# @return [TrueClass,FalseClass] the result of the check
#
# @since 0.7.0
# @api private
def valid?(value)
value.inspect =~ /\[#{primitive}\]/ || super
end
# @since 0.7.0
# @api private
def success(*args)
result(Dry::Types::Result::Success, primitive.new(args.first.to_h))
end
end
end
end
end
end
end
================================================
FILE: lib/hanami/model/sql.rb
================================================
# frozen_string_literal: true
require "rom-sql"
require "hanami/utils"
module Hanami
# Hanami::Model migrations
module Model
require "hanami/model/error"
require "hanami/model/association"
require "hanami/model/migration"
# Define a migration
#
# It must define an up/down strategy to write schema changes (up) and to
# rollback them (down).
#
# We can use <tt>up</tt> and <tt>down</tt> blocks for custom strategies, or
# only one <tt>change</tt> block that automatically implements "down" strategy.
#
# @param blk [Proc] a block that defines up/down or change database migration
#
# @since 0.4.0
#
# @example Use up/down blocks
# Hanami::Model.migration do
# up do
# create_table :books do
# primary_key :id
# column :book, String
# end
# end
#
# down do
# drop_table :books
# end
# end
#
# @example Use change block
# Hanami::Model.migration do
# change do
# create_table :books do
# primary_key :id
# column :book, String
# end
# end
#
# # DOWN strategy is automatically generated
# end
def self.migration(&blk)
Migration.new(configuration.gateways[:default], &blk)
end
# SQL adapter
#
# @since 0.7.0
module Sql
require "hanami/model/sql/types"
require "hanami/model/sql/entity/schema"
# Returns a SQL fragment that references a database function by the given name
# This is useful for database migrations
#
# @param name [String,Symbol] the function name
# @return [String] the SQL fragment
#
# @since 0.7.0
#
# @example
# Hanami::Model.migration do
# up do
# execute 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'
#
# create_table :source_files do
# column :id, 'uuid', primary_key: true, default: Hanami::Model::Sql.function(:uuid_generate_v4)
# # ...
# end
# end
#
# down do
# drop_table :source_files
# execute 'DROP EXTENSION "uuid-ossp"'
# end
# end
def self.function(name)
Sequel.function(name)
end
# Returns a literal SQL fragment for the given SQL fragment.
# This is useful for database migrations
#
# @param string [String] the SQL fragment
# @return [String] the literal SQL fragment
#
# @since 0.7.0
#
# @example
# Hanami::Model.migration do
# up do
# execute %{
# CREATE TYPE inventory_item AS (
# name text,
# supplier_id integer,
# price numeric
# );
# }
#
# create_table :items do
# column :item, 'inventory_item', default: Hanami::Model::Sql.literal("ROW('fuzzy dice', 42, 1.99)")
# # ...
# end
# end
#
# down do
# drop_table :items
# execute 'DROP TYPE inventory_item'
# end
# end
def self.literal(string)
Sequel.lit(string)
end
# Returns SQL fragment for ascending order for the given column
#
# @param column [Symbol] the column name
# @return [String] the SQL fragment
#
# @since 0.7.0
def self.asc(column)
Sequel.asc(column)
end
# Returns SQL fragment for descending order for the given column
#
# @param column [Symbol] the column name
# @return [String] the SQL fragment
#
# @since 0.7.0
def self.desc(column)
Sequel.desc(column)
end
end
Error.register(ROM::SQL::DatabaseError, DatabaseError)
Error.register(ROM::SQL::ConstraintError, ConstraintViolationError)
Error.register(ROM::SQL::NotNullConstraintError, NotNullConstraintViolationError)
Error.register(ROM::SQL::UniqueConstraintError, UniqueConstraintViolationError)
Error.register(ROM::SQL::CheckConstraintError, CheckConstraintViolationError)
Error.register(ROM::SQL::ForeignKeyConstraintError, ForeignKeyConstraintViolationError)
Error.register(ROM::SQL::UnknownDBTypeError, UnknownDatabaseTypeError)
Error.register(ROM::SQL::MissingPrimaryKeyError, MissingPrimaryKeyError)
Error.register(Java::JavaSql::SQLException, DatabaseError) if Utils.jruby?
end
end
Sequel.default_timezone = :utc
ROM.plugins do
adapter :sql do
register :mapping, Hanami::Model::Plugins::Mapping, type: :command
register :schema, Hanami::Model::Plugins::Schema, type: :command
register :timestamps, Hanami::Model::Plugins::Timestamps, type: :command
end
end
================================================
FILE: lib/hanami/model/types.rb
================================================
# frozen_string_literal: true
require "rom/types"
module Hanami
module Model
# Types definitions
#
# @since 0.7.0
module Types
include ROM::Types
# @since 0.7.0
# @api private
def self.included(mod)
mod.extend(ClassMethods)
end
# Class level interface
#
# @since 0.7.0
module ClassMethods
# Define an entity of the given type
#
# @param type [Hanami::Entity] an entity
#
# @since 1.1.0
#
# @example
# require "hanami/model"
#
# class Account < Hanami::Entity
# attributes do
# # ...
# attribute :owner, Types::Entity(User)
# end
# end
#
# account = Account.new(owner: User.new(name: "Luca"))
# account.owner.class # => User
# account.owner.name # => "Luca"
#
# account = Account.new(owner: { name: "MG" })
# account.owner.class # => User
# account.owner.name # => "MG"
def Entity(type)
type = Schema::CoercibleType.new(type) unless type.is_a?(Dry::Types::Definition)
type
end
# Define an array of given type
#
# @param type [Object] an object
#
# @since 0.7.0
#
# @example
# require "hanami/model"
#
# class Account < Hanami::Entity
# attributes do
# # ...
# attribute :users, Types::Collection(User)
# end
# end
#
# account = Account.new(users: [User.new(name: "Luca")])
# user = account.users.first
# user.class # => User
# user.name # => "Luca"
#
# account = Account.new(users: [{ name: "MG" }])
# user = account.users.first
# user.class # => User
# user.name # => "MG"
def Collection(type)
type = Schema::CoercibleType.new(type) unless type.is_a?(Dry::Types::Definition)
Types::Array.member(type)
end
end
# Types for schema definitions
#
# @since 0.7.0
module Schema
# Coercer for objects within custom schema definition
#
# @since 0.7.0
# @api private
class CoercibleType < Dry::Types::Definition
# Coerce given value into the wrapped object type
#
# @param value [Object] the value
#
# @return [Object] the coerced value of `object` type
#
# @raise [TypeError] if value can't be coerced
#
# @since 0.7.0
# @api private
def call(value)
return if value.nil?
if valid?(value)
coerce(value)
else
raise TypeError.new("#{value.inspect} must be coercible into #{object}")
end
end
# Check if value can be coerced
#
# It is true if value is an instance of `object` type or if value
# responds to `#to_hash`.
#
# @param value [Object] the value
#
# @return [TrueClass,FalseClass] the result of the check
#
# @since 0.7.0
# @api private
def valid?(value)
value.is_a?(object) ||
value.respond_to?(:to_hash)
end
# Coerce given value into an instance of `object` type
#
# @param value [Object] the value
#
# @return [Object] the coerced value of `object` type
def coerce(value)
case value
when object
value
else
object.new(value.to_hash)
end
end
# @since 0.7.0
# @api private
def object
result = primitive
return result unless result.respond_to?(:primitive)
result.primitive
end
end
end
end
end
end
================================================
FILE: lib/hanami/model/version.rb
================================================
# frozen_string_literal: true
module Hanami
module Model
# Defines the version
#
# @since 0.1.0
VERSION = "1.3.3"
end
end
================================================
FILE: lib/hanami/model.rb
================================================
# frozen_string_literal: true
require "rom"
require "concurrent"
require "hanami/entity"
require "hanami/repository"
# Hanami
#
# @since 0.1.0
module Hanami
# Hanami persistence
#
# @since 0.1.0
module Model
require "hanami/model/version"
require "hanami/model/error"
require "hanami/model/configuration"
require "hanami/model/configurator"
require "hanami/model/mapping"
require "hanami/model/plugins"
# @api private
# @since 0.7.0
@__repositories__ = Concurrent::Array.new
class << self
# @since 0.7.0
# @api private
attr_reader :config
# @since 0.7.0
# @api private
attr_reader :loaded
# @since 0.7.0
# @api private
alias_method :loaded?, :loaded
end
# Configure the framework
#
# @since 0.1.0
#
# @example
# require 'hanami/model'
#
# Hanami::Model.configure do
# adapter :sql, ENV['DATABASE_URL']
#
# migrations 'db/migrations'
# schema 'db/schema.sql'
# end
def self.configure(&block)
@config = Configurator.build(&block)
self
end
# Current configuration
#
# @since 0.1.0
def self.configuration
@configuration ||= Configuration.new(config)
end
# @since 0.7.0
# @api private
def self.repositories
@__repositories__
end
# @since 0.7.0
# @api private
def self.container
raise "Not loaded" unless loaded?
@container
end
# @since 0.1.0
def self.load!(&blk)
@container = configuration.load!(repositories, &blk)
@loaded = true
end
# Disconnect from the database
#
# This is useful for rebooting applications in production and to ensure that
# the framework prunes stale connections.
#
# @since 1.0.0
#
# @example With Full Stack Hanami Project
# # config/puma.rb
# # ...
# on_worker_boot do
# Hanami.boot
# end
#
# @example With Standalone Hanami::Model
# # config/puma.rb
# # ...
# on_worker_boot do
# Hanami::Model.disconnect
# Hanami::Model.load!
# end
def self.disconnect
configuration.connection&.disconnect
end
end
end
================================================
FILE: lib/hanami/repository.rb
================================================
# frozen_string_literal: true
require "rom-repository"
require "hanami/model/entity_name"
require "hanami/model/relation_name"
require "hanami/model/mapped_relation"
require "hanami/model/associations/dsl"
require "hanami/model/association"
require "hanami/utils/class"
require "hanami/utils/class_attribute"
require "hanami/utils/io"
module Hanami
# Mediates between the entities and the persistence layer, by offering an API
# to query and execute commands on a database.
#
#
#
# By default, a repository is named after an entity, by appending the
# `Repository` suffix to the entity class name.
#
# @example
# require 'hanami/model'
#
# class Article < Hanami::Entity
# end
#
# # valid
# class ArticleRepository < Hanami::Repository
# end
#
# # not valid for Article
# class PostRepository < Hanami::Repository
# end
#
# A repository is storage independent.
# All the queries and commands are delegated to the current adapter.
#
# This architecture has several advantages:
#
# * Applications depend on an abstract API, instead of low level details
# (Dependency Inversion principle)
#
# * Applications depend on a stable API, that doesn't change if the
# storage changes
#
# * Developers can postpone storage decisions
#
# * Isolates the persistence logic at a low level
#
# Hanami::Model is shipped with one adapter:
#
# * SqlAdapter
#
#
#
# All the queries and commands are private.
# This decision forces developers to define intention revealing API, instead
# of leaking storage API details outside of a repository.
#
# @example
# require 'hanami/model'
#
# # This is bad for several reasons:
# #
# # * The caller has an intimate knowledge of the internal mechanisms
# # of the Repository.
# #
# # * The caller works on several levels of abstraction.
# #
# # * It doesn't express a clear intent, it's just a chain of methods.
# #
# # * The caller can't be easily tested in isolation.
# #
# # * If we change the storage, we are forced to change the code of the
# # caller(s).
#
# ArticleRepository.new.where(author_id: 23).order(:published_at).limit(8)
#
#
#
# # This is a huge improvement:
# #
# # * The caller doesn't know how the repository fetches the entities.
# #
# # * The caller works on a single level of abstraction.
# # It doesn't even know about records, only works with entities.
# #
# # * It expresses a clear intent.
# #
# # * The caller can be easily tested in isolation.
# # It's just a matter of stubbing this method.
# #
# # * If we change the storage, the callers aren't affected.
#
# ArticleRepository.new.most_recent_by_author(author)
#
# class ArticleRepository < Hanami::Repository
# def most_recent_by_author(author, limit = 8)
# articles.
# where(author_id: author.id).
# order(:published_at).
# limit(limit)
# end
# end
#
# @since 0.1.0
#
# @see Hanami::Entity
# @see http://martinfowler.com/eaaCatalog/repository.html
# @see http://en.wikipedia.org/wiki/Dependency_inversion_principle
class Repository < ROM::Repository::Root
# Plugins for database commands
#
# @since 0.7.0
# @api private
#
# @see Hanami::Model::Plugins
COMMAND_PLUGINS = %i[schema mapping timestamps].freeze
# Configuration
#
# @since 0.7.0
# @api private
def self.configuration
Hanami::Model.configuration
end
# Container
#
# @since 0.7.0
# @api private
def self.container
Hanami::Model.container
end
# Define a new ROM::Command while preserving the defaults used by Hanami itself.
#
# It allows the user to define a new command to, for example,
# create many records at the same time and still get entities back.
#
# The first argument is the command and relation it will operate on.
#
# @return [ROM::Command] the created command
#
# @example
# # In this example, calling the create_many method with and array of data,
# # would result in the creation of records and return an Array of Task entities.
#
# class TaskRepository < Hanami::Repository
# def create_many(data)
# command(create: :tasks, result: :many).call(data)
# end
# end
#
# @since 1.2.0
def command(*args, **opts, &block)
opts[:use] = COMMAND_PLUGINS | Array(opts[:use])
opts[:mapper] = opts.fetch(:mapper, Model::MappedRelation.mapper_name)
super(*args, **opts, &block)
end
# Define a database relation, which describes how data is fetched from the
# database.
#
# It auto-infers the underlying database table.
#
# @since 0.7.0
# @api private
#
def self.define_relation
a = @associations
s = @schema
configuration.relation(relation) do
if s.nil?
schema(infer: true) do
associations(&a) unless a.nil?
end
else
schema(&s)
end
end
relations(relation)
root(relation)
class_eval %{
def #{relation}
Hanami::Model::MappedRelation.new(@#{relation})
end
}, __FILE__, __LINE__ - 4
end
# Defines the mapping between a database table and an entity.
#
# It's also responsible to associate table columns to entity attributes.
#
# @since 0.7.0
# @api private
#
def self.define_mapping
self.entity = Utils::Class.load!(entity_name)
e = entity
m = @mapping
blk = lambda do |_|
model e
register_as Model::MappedRelation.mapper_name
instance_exec(&m) unless m.nil?
end
root = self.root
configuration.mappers { define(root, &blk) }
configuration.define_mappings(root, &blk)
configuration.register_entity(relation, entity_name.underscore, e)
end
# It defines associations, by adding relations to the repository
#
# @since 0.7.0
# @api private
#
# @see Hanami::Model::Associations::Dsl
def self.define_associations
Model::Associations::Dsl.new(self, &@associations) unless @associations.nil?
end
# Declare associations for the repository
#
# NOTE: This is an experimental feature
#
# @since 0.7.0
# @api private
#
# @example
# class BookRepository < Hanami::Repository
# associations do
# has_many :books
# end
# end
def self.associations(&blk)
@associations = blk
end
# Declare database schema
#
# NOTE: This should be used **only** when Hanami can't find a corresponding Ruby type for your column.
#
# @since 1.0.0
#
# @example
# # In this example `name` is a PostgreSQL Enum type that we want to treat like a string.
#
# class ColorRepository < Hanami::Repository
# schema do
# attribute :id, Hanami::Model::Sql::Types::Int
# attribute :name, Hanami::Model::Sql::Types::String
# attribute :created_at, Hanami::Model::Sql::Types::DateTime
# attribute :updated_at, Hanami::Model::Sql::Types::DateTime
# end
# end
def self.schema(&blk)
@schema = blk
end
# Declare mapping between database columns and entity's attributes
#
# NOTE: This should be used **only** when there is a name mismatch (eg. in legacy databases).
#
# @since 0.7.0
#
# @example
# class BookRepository < Hanami::Repository
# self.relation = :t_operator
#
# mapping do
# attribute :id, from: :operator_id
# attribute :name, from: :s_name
# end
# end
def self.mapping(&blk)
@mapping = blk
end
# Define relations, mapping and associations
#
# @since 0.7.0
# @api private
def self.load!
define_relation
define_mapping
define_associations
end
# @since 0.7.0
# @api private
#
def self.inherited(klass)
klass.class_eval do
include Utils::ClassAttribute
auto_struct true
@associations = nil
@mapping = nil
@schema = nil
class_attribute :entity
class_attribute :entity_name
class_attribute :relation
Hanami::Utils::IO.silence_warnings do
def self.relation=(name)
@relation = name.to_sym
end
end
self.entity_name = Model::EntityName.new(name)
self.relation = Model::RelationName.new(name)
commands :create, update: :by_pk, delete: :by_pk, mapper: Model::MappedRelation.mapper_name, use: COMMAND_PLUGINS
prepend Commands
end
Hanami::Model.repositories << klass
end
# Extend commands from ROM::Repository with error management
#
# @since 0.7.0
module Commands
# Create a new record
#
# @return [Hanami::Entity] a new created entity
#
# @raise [Hanami::Model::Error] an error in case the command fails
#
# @since 0.7.0
#
# @example Create From Hash
# user = UserRepository.new.create(name: 'Luca')
#
# @example Create From Entity
# entity = User.new(name: 'Luca')
# user = UserRepository.new.create(entity)
#
# user.id # => 23
# entity.id # => nil - It doesn't mutate original entity
def create(*args)
super
rescue => exception
raise Hanami::Model::Error.for(exception)
end
# Update a record
#
# @return [Hanami::Entity] an updated entity
#
# @raise [Hanami::Model::Error] an error in case the command fails
#
# @since 0.7.0
#
# @example Update From Data
# repository = UserRepository.new
# user = repository.create(name: 'Luca')
#
# user = repository.update(user.id, age: 34)
#
# @example Update From Entity
# repository = UserRepository.new
# user = repository.create(name: 'Luca')
#
# entity = User.new(age: 34)
# user = repository.update(user.id, entity)
#
# user.age # => 34
# entity.id # => nil - It doesn't mutate original entity
def update(*args)
super
rescue => exception
raise Hanami::Model::Error.for(exception)
end
# Delete a record
#
# @return [Hanami::Entity] a deleted entity
#
# @raise [Hanami::Model::Error] an error in case the command fails
#
# @since 0.7.0
#
# @example
# repository = UserRepository.new
# user = repository.create(name: 'Luca')
#
# user = repository.delete(user.id)
def delete(*args)
super
rescue => exception
raise Hanami::Model::Error.for(exception)
end
end
# Initialize a new instance
#
# @return [Hanami::Repository] the new instance
#
# @since 0.7.0
def initialize
super(self.class.container)
end
# Find by primary key
#
# @return [Hanami::Entity,NilClass] the entity, if found
#
# @raise [Hanami::Model::MissingPrimaryKeyError] if the table doesn't
# define a primary key
#
# @since 0.7.0
#
# @example
# repository = UserRepository.new
# user = repository.create(name: 'Luca')
#
# user = repository.find(user.id)
def find(id)
root.by_pk(id).as(:entity).one
rescue => exception
raise Hanami::Model::Error.for(exception)
end
# Return all the records for the relation
#
# @return [Array<Hanami::Entity>] all the entities
#
# @since 0.7.0
#
# @example
# UserRepository.new.all
def all
root.as(:entity).to_a
end
# Returns the first record for the relation
#
# @return [Hanami::Entity,NilClass] first entity, if any
#
# @since 0.7.0
#
# @example
# UserRepository.new.first
def first
root.as(:entity).limit(1).one
end
# Returns the last record for the relation
#
# @return [Hanami::Entity,NilClass] last entity, if any
#
# @since 0.7.0
#
# @example
# UserRepository.new.last
def last
root.as(:entity).limit(1).reverse.one
end
# Deletes all the records from the relation
#
# @since 0.7.0
#
# @example
# UserRepository.new.clear
def clear
root.delete
end
private
# Returns an association
#
# NOTE: This is an experimental feature
#
# @since 0.7.0
# @api private
def assoc(target, subject = nil)
Hanami::Model::Association.build(self, target, subject)
end
end
end
================================================
FILE: lib/hanami-model.rb
================================================
# frozen_string_literal: true
require "hanami/model"
================================================
FILE: script/ci
================================================
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'
prepare_build() {
if [ -d coverage ]; then
rm -rf coverage
fi
}
print_ruby_version() {
echo "Using $(ruby -v)"
echo
}
run_code_quality_checks() {
bundle exec rubocop .
}
run_unit_tests() {
bundle exec rake spec:unit --trace
}
upload_code_coverage() {
bundle exec rake codecov:upload
}
main() {
prepare_build
print_ruby_version
run_code_quality_checks
run_unit_tests
upload_code_coverage
}
main
================================================
FILE: spec/integration/hanami/model/associations/belongs_to_spec.rb
================================================
# frozen_string_literal: true
RSpec.describe "Associations (belongs_to)" do
it "returns nil if association wasn't preloaded" do
repository = BookRepository.new
book = repository.create(name: "L")
found = repository.find(book.id)
expect(found.author).to be(nil)
end
it "preloads the associated record" do
repository = BookRepository.new
author = AuthorRepository.new.create(name: "Michel Foucault")
book = repository.create(author_id: author.id, title: "Surveiller et punir")
found = repository.find_with_author(book.id)
expect(found).to eq(book)
expect(found.author).to eq(author)
end
it "returns an author" do
repository = BookRepository.new
author = AuthorRepository.new.create(name: "Maurice Leblanc")
book = repository.create(author_id: author.id, title: "L'Aguille Creuse")
found = repository.author_for(book)
expect(found).to eq(author)
end
it "returns nil if there's no associated record" do
repository = BookRepository.new
book = repository.create(title: "The no author book")
expect { repository.find_with_author(book.id) }.to_not raise_error
end
end
================================================
FILE: spec/integration/hanami/model/associations/has_many_spec.rb
================================================
# frozen_string_literal: true
RSpec.describe "Associations (has_many)" do
let(:authors) { AuthorRepository.new }
let(:books) { BookRepository.new }
it "returns nil if association wasn't preloaded" do
author = authors.create(name: "L")
found = authors.find(author.id)
expect(found.books).to be_nil
end
it "preloads associated records" do
author = authors.create(name: "Umberto Eco")
book = books.create(author_id: author.id, title: "Foucault Pendulum")
found = authors.find_with_books(author.id)
expect(found).to eq(author)
expect(found.books).to eq([book])
end
it "creates an object with a collection of associated objects" do
author = authors.create_with_books(name: "Henry Thoreau", books: [{title: "Walden"}])
expect(author).to be_an_instance_of(Author)
expect(author.name).to eq("Henry Thoreau")
expect(author.books).to be_an_instance_of(Array)
expect(author.books.first).to be_an_instance_of(Book)
expect(author.books.first.title).to eq("Walden")
end
it "creates associated records when it receives a collection of serializable data" do
author = authors.create_with_books(name: "Sandi Metz", books: [BaseParams.new(title: "Practical Object-Oriented Design in Ruby")])
expect(author).to be_an_instance_of(Author)
expect(author.name).to eq("Sandi Metz")
expect(author.books).to be_an_instance_of(Array)
expect(author.books.first).to be_an_instance_of(Book)
expect(author.books.first.title).to eq("Practical Object-Oriented Design in Ruby")
end
##############################################################################
# OPERATIONS #
##############################################################################
##
# ADD
#
it "adds an object to the collection" do
author = authors.create(name: "Alexandre Dumas")
book = authors.add_book(author, title: "The Count of Monte Cristo")
expect(book.id).to_not be_nil
expect(book.title).to eq("The Count of Monte Cristo")
expect(book.author_id).to eq(author.id)
end
it "adds an object to the collection with serializable data" do
author = authors.create(name: "David Foster Wallace")
book = authors.add_book(author, BaseParams.new(title: "Infinite Jest"))
expect(book.id).to_not be_nil
expect(book.title).to eq("Infinite Jest")
expect(book.author_id).to eq(author.id)
end
##
# REMOVE
#
it "removes an object from the collection" do
authors = AuthorRepository.new
books = BookRepository.new
# Book under test
author = authors.create(name: "Douglas Adams")
book = books.create(author_id: author.id, title: "The Hitchhiker's Guide to the Galaxy")
# Different book
a = authors.create(name: "William Finnegan")
b = books.create(author_id: a.id, title: "Barbarian Days: A Surfing Life")
authors.remove_book(author, book.id)
# Check the book under test has removed foreign key
found_book = books.find(book.id)
expect(found_book).to_not be_nil
expect(found_book.author_id).to be_nil
found_author = authors.find_with_books(author.id)
expect(found_author.books.map(&:id)).to_not include(found_book.id)
# Check that the other book was left untouched
found_b = books.find(b.id)
expect(found_b.author_id).to eq(a.id)
end
##
# TO_A
#
it "returns an array of books" do
author = authors.create(name: "Nikolai Gogol")
expected = books.create(author_id: author.id, title: "Dead Souls")
expect(expected).to be_an_instance_of(Book)
actual = authors.books_for(author).to_a
expect(actual).to eq([expected])
end
##
# EACH
#
it "iterates through the books" do
author = authors.create(name: "José Saramago")
expected = books.create(author_id: author.id, title: "The Cave")
actual = []
authors.books_for(author).each do |book|
expect(book).to be_an_instance_of(Book)
actual << book
end
expect(actual).to eq([expected])
end
##
# MAP
#
it "iterates through the books and returns an array" do
author = authors.create(name: "José Saramago")
expected = books.create(author_id: author.id, title: "The Cave")
expect(expected).to be_an_instance_of(Book)
actual = authors.books_for(author).map { |book| book }
expect(actual).to eq([expected])
end
##
# COUNT
#
it "returns the count of the associated books" do
author = authors.create(name: "Fyodor Dostoevsky")
books.create(author_id: author.id, title: "Crime and Punishment")
books.create(author_id: author.id, title: "The Brothers Karamazov")
expect(authors.books_count(author)).to eq(2)
end
it "returns the count of on sale associated books" do
author = authors.create(name: "Steven Pinker")
books.create(author_id: author.id, title: "The Sense of Style", on_sale: true)
expect(authors.on_sales_books_count(author)).to eq(1)
end
##
# DELETE
#
it "deletes all the books" do
author = authors.create(name: "Grazia Deledda")
book = books.create(author_id: author.id, title: "Reeds In The Wind")
authors.delete_books(author)
expect(books.find(book.id)).to be_nil
end
it "deletes scoped books" do
author = authors.create(name: "Harper Lee")
book = books.create(author_id: author.id, title: "To Kill A Mockingbird")
on_sale = books.create(author_id: author.id, title: "Go Set A Watchman", on_sale: true)
authors.delete_on_sales_books(author)
expect(books.find(book.id)).to eq(book)
expect(books.find(on_sale.id)).to be_nil
end
context "raises a Hanami::Model::Error wrapped exception on" do
it "#create" do
expect do
authors.create_with_books(name: "Noam Chomsky")
end.to raise_error Hanami::Model::Error
end
it "#add" do
author = authors.create(name: "Machado de Assis")
expect do
authors.add_book(author, title: "O Alienista", on_sale: nil)
end.to raise_error Hanami::Model::NotNullConstraintViolationError
end
# skipped spec
it "#remove"
end
end
================================================
FILE: spec/integration/hanami/model/associations/has_one_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
RSpec.describe "Associations (has_one)" do
extend PlatformHelpers
let(:users) { UserRepository.new }
let(:avatars) { AvatarRepository.new }
it "returns nil if the association wasn't preloaded" do
user = users.create(name: "John Doe")
found = users.find(user.id)
expect(found.avatar).to be_nil
end
it "preloads the associated record" do
user = users.create(name: "Baruch Spinoza")
avatar = avatars.create(user_id: user.id, url: "http://www.notarealurl.com/avatar.png")
found = users.find_with_avatar(user.id)
expect(found).to eq(user)
expect(found.avatar).to eq(avatar)
end
it "returns an Avatar" do
user = users.create(name: "Simone de Beauvoir")
avatar = avatars.create(user_id: user.id, url: "http://www.notarealurl.com/simone.png")
found = users.avatar_for(user)
expect(found).to eq(avatar)
end
it "returns nil if the association was preloaded but no associated object is set" do
user = users.create(name: "Henry Jenkins")
found = users.find_with_avatar(user.id)
expect(found).to eq(user)
expect(found.avatar).to be_nil
end
context "#add" do
it "adds an an Avatar to an existing User" do
user = users.create(name: "Jean Paul-Sartre")
avatar = users.add_avatar(user, url: "http://www.notarealurl.com/sartre.png")
found = users.find_with_avatar(user.id)
expect(found).to eq(user)
expect(found.avatar.id).to eq(avatar.id)
expect(found.avatar.url).to eq("http://www.notarealurl.com/sartre.png")
end
it "adds an an Avatar to an existing User when serializable data is received" do
user = users.create(name: "Jean Paul-Sartre")
avatar = users.add_avatar(user, BaseParams.new(url: "http://www.notarealurl.com/sartre.png"))
found = users.find_with_avatar(user.id)
expect(found).to eq(user)
expect(found.avatar.id).to eq(avatar.id)
expect(found.avatar.url).to eq("http://www.notarealurl.com/sartre.png")
end
end
context "#update" do
it "updates the avatar" do
user = users.create_with_avatar(name: "Bakunin", avatar: {url: "bakunin.jpg"})
users.update_avatar(user, url: url = "http://history.com/bakunin.png")
found = users.find_with_avatar(user.id)
expect(found).to eq(user)
expect(found.avatar).to eq(user.avatar)
expect(found.avatar.url).to eq(url)
end
it "updates the avatar when serializable data is received" do
user = users.create_with_avatar(name: "Bakunin", avatar: {url: "bakunin.jpg"})
users.update_avatar(user, BaseParams.new(url: url = "http://history.com/bakunin.png"))
found = users.find_with_avatar(user.id)
expect(found).to eq(user)
expect(found.avatar).to eq(user.avatar)
expect(found.avatar.url).to eq(url)
end
end
context "#create" do
it "creates a User and an Avatar" do
user = users.create_with_avatar(name: "Lao Tse", avatar: {url: "http://lao-tse.io/me.jpg"})
found = users.find_with_avatar(user.id)
expect(found.name).to eq(user.name)
expect(found.avatar).to eq(user.avatar)
expect(found.avatar.url).to eq("http://lao-tse.io/me.jpg")
end
it "creates a User and an Avatar when serializable data is received" do
user = users.create_with_avatar(name: "Lao Tse", avatar: BaseParams.new(url: "http://lao-tse.io/me.jpg"))
found = users.find_with_avatar(user.id)
expect(found.name).to eq(user.name)
expect(found.avatar).to eq(user.avatar)
expect(found.avatar.url).to eq("http://lao-tse.io/me.jpg")
end
end
context "#delete" do
it "removes the Avatar" do
user = users.create_with_avatar(name: "Bob Ross", avatar: {url: "http://bobross/happy_little_avatar.jpg"})
other = users.create_with_avatar(name: "Candido Portinari", avatar: {url: "some_mugshot.jpg"})
users.remove_avatar(user)
found = users.find_with_avatar(user.id)
other_found = users.find_with_avatar(other.id)
expect(found.avatar).to be_nil
expect(other_found.avatar).to be_an Avatar
end
end
context "#replace" do
it "replaces the associated object" do
user = users.create_with_avatar(name: "Frank Herbert", avatar: {url: "http://not-real.com/avatar.jpg"})
users.replace_avatar(user, url: "http://totally-correct.com/avatar.jpg")
found = users.find_with_avatar(user.id)
expect(found.avatar).to_not eq(user.avatar)
expect(avatars.by_user(user.id).size).to eq(1)
end
it "replaces the associated object when serializable data is received" do
user = users.create_with_avatar(name: "Frank Herbert", avatar: {url: "http://not-real.com/avatar.jpg"})
users.replace_avatar(user, BaseParams.new(url: "http://totally-correct.com/avatar.jpg"))
found = users.find_with_avatar(user.id)
expect(found.avatar).to_not eq(user.avatar)
expect(avatars.by_user(user.id).size).to eq(1)
end
end
context "raises a Hanami::Model::Error wrapped exception on" do
it "#create" do
expect do
users.create_with_avatar(name: "Noam Chomsky")
end.to raise_error Hanami::Model::Error
end
it "#add" do
user = users.create_with_avatar(name: "Stephen Fry", avatar: {url: "fry_mugshot.png"})
expect { users.add_avatar(user, url: "new_mugshot.png") }.to raise_error Hanami::Model::UniqueConstraintViolationError
end
# by default it seems that MySQL allows you to update a NOT NULL column to a NULL value
unless_platform(db: :mysql) do
it "#update" do
user = users.create_with_avatar(name: "Dan North", avatar: {url: "bdd_creator.png"})
expect do
users.update_avatar(user, url: nil)
end.to raise_error Hanami::Model::NotNullConstraintViolationError
end
end
it "#replace" do
user = users.create_with_avatar(name: "Eric Evans", avatar: {url: "ddd_man.png"})
expect { users.replace_avatar(user, url: nil) }.to raise_error Hanami::Model::NotNullConstraintViolationError
end
end
end
================================================
FILE: spec/integration/hanami/model/associations/many_to_many_spec.rb
================================================
# frozen_string_literal: true
RSpec.describe "Associations (has_many :through)" do
#### REPOS
let(:books) { BookRepository.new }
let(:categories) { CategoryRepository.new }
let(:ontologies) { BookOntologyRepository.new }
### ENTITIES
let(:book) { books.create(title: "Ontology: Encyclopedia of Database Systems") }
let(:category) { categories.create(name: "information science") }
it "returns nil if association wasn't preloaded" do
found = books.find(book.id)
expect(found.categories).to be(nil)
end
it "preloads the associated record" do
ontologies.create(book_id: book.id, category_id: category.id)
found = books.find_with_categories(book.id)
expect(found).to eq(book)
expect(found.categories).to eq([category])
end
it "returns an array of Categories" do
ontologies.create(book_id: book.id, category_id: category.id)
found = books.categories_for(book)
expect(found).to eq([category])
end
it "returns the count of on sale associated books" do
on_sale = books.create(title: "The Sense of Style", on_sale: true)
ontologies.create(book_id: on_sale.id, category_id: category.id)
expect(categories.on_sales_books_count(category)).to eq(1)
end
context "#add" do
it "adds an object to the collection" do
books.add_category(book, category)
found_book = books.find_with_categories(book.id)
found_category = categories.find_with_books(category.id)
expect(found_book).to eq(book)
expect(found_book.categories).to eq([category])
expect(found_category).to eq(category)
expect(found_category.books).to eq([book])
end
it "associates a collection of records" do
other_book = books.create(title: "Ontological Engineering")
categories.add_books(category, book, other_book)
found = categories.find_with_books(category.id)
expect(found.books).to match_array([book, other_book])
end
end
context "#delete" do
it "removes all association information" do
books.add_category(book, category)
categorized = books.find_with_categories(book.id)
books.clear_categories(book)
found = books.find_with_categories(book.id)
expect(categorized.categories).to be_an Array
expect(categorized.categories).to match_array([category])
expect(found.categories).to be_empty
expect(found).to eq(categorized)
end
it "does not touch other books" do
other_book = books.create(title: "Do not meddle with")
books.add_category(other_book, category)
books.add_category(book, category)
books.clear_categories(book)
found = books.find_with_categories(book.id)
other_found = books.find_with_categories(other_book.id)
expect(found).to eq(book)
expect(other_book).to eq(other_found)
expect(other_found.categories).to eq([category])
expect(found.categories).to be_empty
end
end
context "#remove" do
it "removes the desired association" do
to_remove = books.create(title: "The Life of a Stoic")
books.add_category(to_remove, category)
categories.remove_book(category, to_remove.id)
found = categories.find_with_books(category.id)
expect(found.books).to_not include(to_remove)
end
end
context "collection methods" do
it "returns an array of books" do
ontologies.create(book_id: book.id, category_id: category.id)
actual = categories.books_for(category).to_a
expect(actual).to eq([book])
end
it "iterates through the categories" do
ontologies.create(book_id: book.id, category_id: category.id)
actual = []
categories.books_for(category).each do |book|
expect(book).to be_an_instance_of(Book)
actual << book
end
expect(actual).to eq([book])
end
it "iterates through the books and returns an array" do
ontologies.create(book_id: book.id, category_id: category.id)
actual = categories.books_for(category).map(&:id)
expect(actual).to eq([book.id])
end
it "returns the count of the associated books" do
other_book = books.create(title: "Practical Ontologies for Information Professionals")
ontologies.create(book_id: book.id, category_id: category.id)
ontologies.create(book_id: other_book.id, category_id: category.id)
expect(categories.books_count(category)).to eq(2)
end
end
context "raises a Hanami::Model::Error wrapped exception on" do
it "#add" do
expect do
categories.add_books(category, id: -2)
end.to raise_error Hanami::Model::ForeignKeyConstraintViolationError
end
end
end
================================================
FILE: spec/integration/hanami/model/associations/relation_alias_spec.rb
================================================
# frozen_string_literal: true
RSpec.describe "Alias (:as) support for associations" do
let(:users) { UserRepository.new }
let(:posts) { PostRepository.new }
let(:comments) { CommentRepository.new }
it "the attribute is named after the association" do
user = users.create(name: "Jules Verne")
post = posts.create(title: "World Traveling made easy", user_id: user.id)
post_found = posts.find_with_author(post.id)
expect(post_found.author).to eq(user)
user_found = users.find_with_threads(user.id)
expect(user_found.threads).to match_array([post])
end
it "it works with nested aggregates" do
user = users.create(name: "Jules Verne")
post = posts.create(title: "World Traveling made easy", user_id: user.id)
commenter = users.create(name: "Thomas Reid")
comments.create(user_id: commenter.id, post_id: post.id)
found = posts.feed_for(post.id)
expect(found.author).to eq(user)
expect(found.comments[0].user).to eq(commenter)
end
context "#assoc support (calling assoc by the alias)" do
it "for #belongs_to" do
user = users.create(name: "Jules Verne")
post = posts.create(title: "World Traveling made easy", user_id: user.id)
commenter = users.create(name: "Thomas Reid")
comment = comments.create(user_id: commenter.id, post_id: post.id)
found_author = posts.author_for(post)
expect(found_author).to eq(user)
found_commenter = comments.commenter_for(comment)
expect(found_commenter).to eq(commenter)
end
it "for #has_many" do
user = users.create(name: "Jules Verne")
post = posts.create(title: "World Traveling made easy", user_id: user.id)
found_threads = users.threads_for(user)
expect(found_threads).to match_array [post]
end
it "for #has_many :through" do
user = users.create(name: "Jules Verne")
post = posts.create(title: "World Traveling made easy", user_id: user.id)
commenter = users.create(name: "Thomas Reid")
comments.create(user_id: commenter.id, post_id: post.id)
commenters = posts.commenters_for(post)
expect(commenters).to match_array([commenter])
end
end
end
================================================
FILE: spec/integration/hanami/model/migration/mysql.rb
================================================
# frozen_string_literal: true
RSpec.shared_examples "migration_integration_mysql" do
before do
@schema = Pathname.new("#{__dir__}/../../../../../tmp/schema.sql").expand_path
@connection = Sequel.connect(ENV["HANAMI_DATABASE_URL"])
Hanami::Model::Migrator::Adapter.for(Hanami::Model.configuration).dump
end
describe "columns" do
it "defines column types" do
table = @connection.schema(:column_types)
name, options = table[0]
expect(name).to eq(:integer1)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:integer)
expect(options.fetch(:db_type)).to eq("int")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[1]
expect(name).to eq(:integer2)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:integer)
expect(options.fetch(:db_type)).to eq("int")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[2]
expect(name).to eq(:integer3)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:integer)
expect(options.fetch(:db_type)).to eq("int")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[3]
expect(name).to eq(:string1)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:string)
expect(options.fetch(:db_type)).to eq("varchar(255)")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[4]
expect(name).to eq(:string2)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:string)
expect(options.fetch(:db_type)).to eq("varchar(3)")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[5]
expect(name).to eq(:string5)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:string)
expect(options.fetch(:db_type)).to eq("varchar(50)")
expect(options.fetch(:max_length)).to eq(50)
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[6]
expect(name).to eq(:string6)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:string)
expect(options.fetch(:db_type)).to eq("char(255)")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[7]
expect(name).to eq(:string7)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:string)
expect(options.fetch(:db_type)).to eq("char(64)")
expect(options.fetch(:max_length)).to eq(64)
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[8]
expect(name).to eq(:string8)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:string)
expect(options.fetch(:db_type)).to eq("text")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[9]
expect(name).to eq(:file1)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:blob)
expect(options.fetch(:db_type)).to eq("blob")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[10]
expect(name).to eq(:file2)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:blob)
expect(options.fetch(:db_type)).to eq("blob")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[11]
expect(name).to eq(:number1)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:integer)
expect(options.fetch(:db_type)).to eq("int")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[12]
expect(name).to eq(:number2)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:integer)
expect(options.fetch(:db_type)).to eq("bigint")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[13]
expect(name).to eq(:number3)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:float)
expect(options.fetch(:db_type)).to eq("double")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[14]
expect(name).to eq(:number4)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:integer)
expect(options.fetch(:db_type)).to eq("decimal(10,0)")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[15]
expect(name).to eq(:number5)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
# expect(options.fetch(:type)).to eq(:decimal)
expect(options.fetch(:db_type)).to eq("decimal(10,0)")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[16]
expect(name).to eq(:number6)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:decimal)
expect(options.fetch(:db_type)).to eq("decimal(10,2)")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[17]
expect(name).to eq(:number7)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:integer)
expect(options.fetch(:db_type)).to eq("decimal(10,0)")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[18]
expect(name).to eq(:date1)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:date)
expect(options.fetch(:db_type)).to eq("date")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[19]
expect(name).to eq(:date2)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:datetime)
expect(options.fetch(:db_type)).to eq("datetime")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[20]
expect(name).to eq(:time1)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:datetime)
expect(options.fetch(:db_type)).to eq("datetime")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[21]
expect(name).to eq(:time2)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:time)
expect(options.fetch(:db_type)).to eq("time")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[22]
expect(name).to eq(:boolean1)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:boolean)
expect(options.fetch(:db_type)).to eq("tinyint(1)")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[23]
expect(name).to eq(:boolean2)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq(nil)
expect(options.fetch(:type)).to eq(:boolean)
expect(options.fetch(:db_type)).to eq("tinyint(1)")
expect(options.fetch(:primary_key)).to eq(false)
end
it "defines column defaults" do
table = @connection.schema(:default_values)
name, options = table[0]
expect(name).to eq(:a)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq("23")
expect(options.fetch(:ruby_default)).to eq(23)
expect(options.fetch(:type)).to eq(:integer)
expect(options.fetch(:db_type)).to eq("int")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[1]
expect(name).to eq(:b)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq("Hanami")
expect(options.fetch(:ruby_default)).to eq("Hanami")
expect(options.fetch(:type)).to eq(:string)
expect(options.fetch(:db_type)).to eq("varchar(255)")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[2]
expect(name).to eq(:c)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq("-1")
expect(options.fetch(:ruby_default)).to eq(-1)
expect(options.fetch(:type)).to eq(:integer)
expect(options.fetch(:db_type)).to eq("int")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[3]
expect(name).to eq(:d)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq("0")
expect(options.fetch(:ruby_default)).to eq(0)
expect(options.fetch(:type)).to eq(:integer)
expect(options.fetch(:db_type)).to eq("bigint")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[4]
expect(name).to eq(:e)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq("3.14")
expect(options.fetch(:ruby_default)).to eq(3.14)
expect(options.fetch(:type)).to eq(:float)
expect(options.fetch(:db_type)).to eq("double")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[5]
expect(name).to eq(:f)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq("1")
expect(options.fetch(:ruby_default)).to eq(1.0)
expect(options.fetch(:type)).to eq(:integer)
expect(options.fetch(:db_type)).to eq("decimal(10,0)")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[6]
expect(name).to eq(:g)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq("943943")
expect(options.fetch(:ruby_default)).to eq(943_943)
expect(options.fetch(:type)).to eq(:integer)
expect(options.fetch(:db_type)).to eq("decimal(10,0)")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[10]
expect(name).to eq(:k)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq("1")
expect(options.fetch(:ruby_default)).to eq(true)
expect(options.fetch(:type)).to eq(:boolean)
expect(options.fetch(:db_type)).to eq("tinyint(1)")
expect(options.fetch(:primary_key)).to eq(false)
name, options = table[11]
expect(name).to eq(:l)
expect(options.fetch(:allow_null)).to eq(true)
expect(options.fetch(:default)).to eq("0")
expect(options.fetch(:ruby_default)).to eq(false)
expect(options.fetch(:type)).to
gitextract_p2apyb1u/
├── .github/
│ ├── FUNDING.yml
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── .jrubyrc
├── .rspec
├── .rubocop.yml
├── .yardopts
├── CHANGELOG.md
├── Gemfile
├── LICENSE.md
├── README.md
├── Rakefile
├── hanami-model.gemspec
├── lib/
│ ├── hanami/
│ │ ├── entity/
│ │ │ └── schema.rb
│ │ ├── entity.rb
│ │ ├── model/
│ │ │ ├── association.rb
│ │ │ ├── associations/
│ │ │ │ ├── belongs_to.rb
│ │ │ │ ├── dsl.rb
│ │ │ │ ├── has_many.rb
│ │ │ │ ├── has_one.rb
│ │ │ │ └── many_to_many.rb
│ │ │ ├── configuration.rb
│ │ │ ├── configurator.rb
│ │ │ ├── entity_name.rb
│ │ │ ├── error.rb
│ │ │ ├── mapped_relation.rb
│ │ │ ├── mapping.rb
│ │ │ ├── migration.rb
│ │ │ ├── migrator/
│ │ │ │ ├── adapter.rb
│ │ │ │ ├── connection.rb
│ │ │ │ ├── logger.rb
│ │ │ │ ├── mysql_adapter.rb
│ │ │ │ ├── postgres_adapter.rb
│ │ │ │ └── sqlite_adapter.rb
│ │ │ ├── migrator.rb
│ │ │ ├── plugins/
│ │ │ │ ├── mapping.rb
│ │ │ │ ├── schema.rb
│ │ │ │ └── timestamps.rb
│ │ │ ├── plugins.rb
│ │ │ ├── relation_name.rb
│ │ │ ├── sql/
│ │ │ │ ├── console.rb
│ │ │ │ ├── consoles/
│ │ │ │ │ ├── abstract.rb
│ │ │ │ │ ├── mysql.rb
│ │ │ │ │ ├── postgresql.rb
│ │ │ │ │ └── sqlite.rb
│ │ │ │ ├── entity/
│ │ │ │ │ └── schema.rb
│ │ │ │ ├── types/
│ │ │ │ │ └── schema/
│ │ │ │ │ └── coercions.rb
│ │ │ │ └── types.rb
│ │ │ ├── sql.rb
│ │ │ ├── types.rb
│ │ │ └── version.rb
│ │ ├── model.rb
│ │ └── repository.rb
│ └── hanami-model.rb
├── script/
│ └── ci
└── spec/
├── integration/
│ └── hanami/
│ └── model/
│ ├── associations/
│ │ ├── belongs_to_spec.rb
│ │ ├── has_many_spec.rb
│ │ ├── has_one_spec.rb
│ │ ├── many_to_many_spec.rb
│ │ └── relation_alias_spec.rb
│ ├── migration/
│ │ ├── mysql.rb
│ │ ├── postgresql.rb
│ │ └── sqlite.rb
│ ├── migration_spec.rb
│ └── repository/
│ ├── base_spec.rb
│ ├── command_spec.rb
│ └── legacy_spec.rb
├── spec_helper.rb
├── support/
│ ├── database/
│ │ └── strategies/
│ │ ├── abstract.rb
│ │ ├── mysql.rb
│ │ ├── postgresql.rb
│ │ ├── sql.rb
│ │ └── sqlite.rb
│ ├── database.rb
│ ├── fixtures/
│ │ ├── database_migrations/
│ │ │ ├── 20150612081248_column_types.rb
│ │ │ ├── 20150612084656_default_values.rb
│ │ │ ├── 20150612093458_null_constraints.rb
│ │ │ ├── 20150612093810_column_indexes.rb
│ │ │ ├── 20150612094740_primary_keys.rb
│ │ │ ├── 20150612115204_foreign_keys.rb
│ │ │ ├── 20150612122233_table_constraints.rb
│ │ │ ├── 20150612124205_table_alterations.rb
│ │ │ ├── 20160830094800_create_users.rb
│ │ │ ├── 20160830094851_create_authors.rb
│ │ │ ├── 20160830094941_create_books.rb
│ │ │ ├── 20160830095033_create_t_operator.rb
│ │ │ ├── 20160905125728_create_source_files.rb
│ │ │ ├── 20160909150704_create_avatars.rb
│ │ │ ├── 20161104143844_create_warehouses.rb
│ │ │ ├── 20161114094644_create_products.rb
│ │ │ ├── 20170103142428_create_colors.rb
│ │ │ ├── 20170124081339_create_labels.rb
│ │ │ ├── 20170517115243_create_tokens.rb
│ │ │ ├── 20170519172332_create_categories.rb
│ │ │ └── 20171002201227_create_posts_and_comments.rb
│ │ ├── empty_migrations/
│ │ │ └── .gitkeep
│ │ └── migrations/
│ │ ├── 20160831073534_create_reviews.rb
│ │ └── 20160831090612_add_rating_to_reviews.rb
│ ├── fixtures.rb
│ ├── platform/
│ │ ├── ci.rb
│ │ ├── db.rb
│ │ ├── engine.rb
│ │ ├── matcher.rb
│ │ └── os.rb
│ ├── platform.rb
│ ├── rspec.rb
│ └── test_io.rb
└── unit/
└── hanami/
├── entity/
│ ├── automatic_schema_spec.rb
│ ├── manual_schema/
│ │ ├── base_spec.rb
│ │ ├── strict_spec.rb
│ │ └── types_spec.rb
│ ├── schema/
│ │ ├── definition_spec.rb
│ │ └── schemaless_spec.rb
│ ├── schema_spec.rb
│ └── schemaless_spec.rb
├── entity_spec.rb
└── model/
├── check_constraint_validation_error_spec.rb
├── configuration_spec.rb
├── constraint_violation_error_spec.rb
├── disconnect_spec.rb
├── error_spec.rb
├── foreign_key_constraint_violation_error_spec.rb
├── load_spec.rb
├── mapped_relation_spec.rb
├── migrator/
│ ├── adapter_spec.rb
│ ├── connection_spec.rb
│ ├── mysql.rb
│ ├── postgresql.rb
│ └── sqlite.rb
├── migrator_spec.rb
├── not_null_constraint_violation_error_spec.rb
├── sql/
│ ├── console/
│ │ ├── mysql.rb
│ │ ├── postgresql.rb
│ │ └── sqlite.rb
│ ├── console_spec.rb
│ ├── entity/
│ │ └── schema/
│ │ ├── automatic_spec.rb
│ │ └── mapping_spec.rb
│ └── schema/
│ ├── array_spec.rb
│ ├── bool_spec.rb
│ ├── date_spec.rb
│ ├── date_time_spec.rb
│ ├── decimal_spec.rb
│ ├── float_spec.rb
│ ├── hash_spec.rb
│ ├── int_spec.rb
│ ├── string_spec.rb
│ └── time_spec.rb
├── sql_spec.rb
├── unique_constraint_violation_error_spec.rb
└── version_spec.rb
SYMBOL INDEX (773 symbols across 69 files)
FILE: lib/hanami/entity.rb
type Hanami (line 5) | module Hanami
class Entity (line 54) | class Entity
type Types (line 60) | module Types
type ClassMethods (line 68) | module ClassMethods
function attributes (line 86) | def attributes(type = nil, &blk)
function schema= (line 97) | def schema=(value)
method inherited (line 110) | def self.inherited(klass)
method initialize (line 126) | def initialize(attributes = nil)
method id (line 136) | def id
method method_missing (line 146) | def method_missing(method_name, *)
method == (line 161) | def ==(other)
method hash (line 171) | def hash
method freeze (line 178) | def freeze
method to_h (line 188) | def to_h
method attribute? (line 201) | def attribute?(name)
method respond_to_missing? (line 213) | def respond_to_missing?(name, _include_all)
FILE: lib/hanami/entity/schema.rb
type Hanami (line 6) | module Hanami
class Entity (line 7) | class Entity
class Schema (line 56) | class Schema
class Schemaless (line 61) | class Schemaless
method initialize (line 64) | def initialize
method call (line 74) | def call(attributes)
method attribute? (line 84) | def attribute?(_name)
class Definition (line 93) | class Definition
class Dsl (line 97) | class Dsl
method build (line 108) | def self.build(type, &blk)
method initialize (line 118) | def initialize(&blk)
method attribute (line 149) | def attribute(name, type)
method to_h (line 155) | def to_h
method initialize (line 168) | def initialize(type = nil, &blk)
method call (line 185) | def call(attributes)
method attribute? (line 201) | def attribute?(name)
method initialize (line 224) | def initialize(type = nil, &blk)
method call (line 240) | def call(attributes)
method attribute? (line 258) | def attribute?(name)
FILE: lib/hanami/model.rb
type Hanami (line 11) | module Hanami
type Model (line 15) | module Model
function configure (line 54) | def self.configure(&block)
function configuration (line 62) | def self.configuration
function repositories (line 68) | def self.repositories
function container (line 74) | def self.container
function load! (line 81) | def self.load!(&blk)
function disconnect (line 107) | def self.disconnect
FILE: lib/hanami/model/association.rb
type Hanami (line 9) | module Hanami
type Model (line 10) | module Model
class Association (line 15) | class Association
method build (line 20) | def self.build(repository, target, subject)
method lookup (line 29) | def self.lookup(association)
FILE: lib/hanami/model/associations/belongs_to.rb
type Hanami (line 5) | module Hanami
type Model (line 6) | module Model
type Associations (line 7) | module Associations
class BelongsTo (line 12) | class BelongsTo
method schema_type (line 15) | def self.schema_type(entity)
method initialize (line 41) | def initialize(repository, source, target, subject, scope = nil)
method one (line 52) | def one
method container (line 60) | def container
method primary_key (line 66) | def primary_key
method relation (line 72) | def relation(name)
method foreign_key (line 78) | def foreign_key
method association_keys (line 86) | def association_keys
method association (line 95) | def association
method _build_scope (line 101) | def _build_scope
FILE: lib/hanami/model/associations/dsl.rb
type Hanami (line 3) | module Hanami
type Model (line 4) | module Model
type Associations (line 5) | module Associations
class Dsl (line 11) | class Dsl
method initialize (line 14) | def initialize(repository, &blk)
method has_many (line 21) | def has_many(relation, **args)
method has_one (line 28) | def has_one(relation, *)
method belongs_to (line 34) | def belongs_to(relation, *)
FILE: lib/hanami/model/associations/has_many.rb
type Hanami (line 5) | module Hanami
type Model (line 6) | module Model
type Associations (line 7) | module Associations
class HasMany (line 12) | class HasMany
method schema_type (line 15) | def self.schema_type(entity)
method initialize (line 42) | def initialize(repository, source, target, subject, scope = nil)
method create (line 53) | def create(data)
method add (line 62) | def add(data)
method remove (line 71) | def remove(id)
method delete (line 79) | def delete
method each (line 85) | def each(&blk)
method map (line 91) | def map(&blk)
method to_a (line 97) | def to_a
method where (line 103) | def where(condition)
method count (line 109) | def count
method command (line 117) | def command(target, relation, options = {})
method entity (line 123) | def entity
method relation (line 129) | def relation(name)
method aggregate (line 135) | def aggregate(name)
method association (line 141) | def association(name)
method associate (line 147) | def associate(data)
method unassociate (line 155) | def unassociate
method container (line 161) | def container
method primary_key (line 167) | def primary_key
method foreign_key (line 173) | def foreign_key
method association_keys (line 181) | def association_keys
method target_association (line 190) | def target_association
method _build_scope (line 196) | def _build_scope
method __new__ (line 204) | def __new__(new_scope)
method serialize (line 208) | def serialize(data)
FILE: lib/hanami/model/associations/has_one.rb
type Hanami (line 5) | module Hanami
type Model (line 6) | module Model
type Associations (line 7) | module Associations
class HasOne (line 12) | class HasOne
method schema_type (line 15) | def self.schema_type(entity)
method initialize (line 41) | def initialize(repository, source, target, subject, scope = nil)
method one (line 50) | def one
method create (line 54) | def create(data)
method add (line 62) | def add(data)
method update (line 68) | def update(data)
method delete (line 77) | def delete
method replace (line 82) | def replace(data)
method entity (line 93) | def entity
method aggregate (line 99) | def aggregate(name)
method command (line 105) | def command(target, relation, options = {})
method relation (line 111) | def relation(name)
method container (line 117) | def container
method primary_key (line 123) | def primary_key
method foreign_key (line 129) | def foreign_key
method associate (line 135) | def associate(data)
method association_keys (line 145) | def association_keys
method _build_scope (line 153) | def _build_scope
method serialize (line 161) | def serialize(data)
FILE: lib/hanami/model/associations/many_to_many.rb
type Hanami (line 5) | module Hanami
type Model (line 6) | module Model
type Associations (line 7) | module Associations
class ManyToMany (line 12) | class ManyToMany
method schema_type (line 15) | def self.schema_type(entity)
method initialize (line 44) | def initialize(repository, source, target, subject, scope = nil)
method to_a (line 54) | def to_a
method map (line 58) | def map(&blk)
method each (line 62) | def each(&blk)
method count (line 66) | def count
method where (line 70) | def where(condition)
method add (line 78) | def add(*data)
method delete (line 87) | def delete
method remove (line 93) | def remove(target_id)
method container (line 108) | def container
method relation (line 114) | def relation(name)
method command (line 120) | def command(target, relation, options = {})
method associate (line 126) | def associate(data)
method source_primary_key (line 134) | def source_primary_key
method source_foreign_key (line 140) | def source_foreign_key
method association_keys (line 146) | def association_keys
method target_foreign_key (line 154) | def target_foreign_key
method target_primary_key (line 160) | def target_primary_key
method association (line 168) | def association
method _build_scope (line 175) | def _build_scope
method __new__ (line 187) | def __new__(new_scope)
method serialize (line 193) | def serialize(data)
FILE: lib/hanami/model/configuration.rb
type Hanami (line 5) | module Hanami
type Model (line 6) | module Model
class Configuration (line 13) | class Configuration
method initialize (line 32) | def initialize(configurator)
method connection (line 57) | def connection
method gateway (line 68) | def gateway
method root (line 76) | def root
method migrations (line 83) | def migrations
method schema (line 90) | def schema
method define_mappings (line 96) | def define_mappings(root, &blk)
method register_entity (line 102) | def register_entity(plural, singular, klass)
method define_entities_mappings (line 109) | def define_entities_mappings(container, repositories)
method configure_gateway (line 122) | def configure_gateway
method logger= (line 128) | def logger=(value)
method rom (line 139) | def rom
method load! (line 152) | def load!(repositories, &blk)
method method_missing (line 168) | def method_missing(method_name, *args, &blk)
method respond_to_missing? (line 178) | def respond_to_missing?(method_name, include_all)
FILE: lib/hanami/model/configurator.rb
type Hanami (line 3) | module Hanami
type Model (line 4) | module Model
class Configurator (line 9) | class Configurator
method build (line 40) | def self.build(&block)
method migrations_logger (line 46) | def migrations_logger(stream = $stdout)
method adapter (line 55) | def adapter(backend, url)
method path (line 62) | def path(path)
method migrations (line 68) | def migrations(path)
method schema (line 74) | def schema(path)
method logger (line 80) | def logger(stream, options = {})
method gateway (line 89) | def gateway(&blk)
FILE: lib/hanami/model/entity_name.rb
type Hanami (line 3) | module Hanami
type Model (line 4) | module Model
class EntityName (line 12) | class EntityName
method initialize (line 22) | def initialize(name)
method underscore (line 28) | def underscore
method to_s (line 34) | def to_s
FILE: lib/hanami/model/error.rb
type Hanami (line 5) | module Hanami
type Model (line 6) | module Model
class Error (line 10) | class Error < ::StandardError
method for (line 17) | def self.for(exception)
method register (line 23) | def self.register(external, internal)
method mapping (line 29) | def self.mapping
class DatabaseError (line 37) | class DatabaseError < Error
class InvalidCommandError (line 43) | class InvalidCommandError < Error
method initialize (line 46) | def initialize(message = "Invalid command")
class ConstraintViolationError (line 54) | class ConstraintViolationError < Error
method initialize (line 57) | def initialize(message = "Constraint has been violated")
class UniqueConstraintViolationError (line 65) | class UniqueConstraintViolationError < ConstraintViolationError
method initialize (line 68) | def initialize(message = "Unique constraint has been violated")
class ForeignKeyConstraintViolationError (line 76) | class ForeignKeyConstraintViolationError < ConstraintViolationError
method initialize (line 79) | def initialize(message = "Foreign key constraint has been violated")
class NotNullConstraintViolationError (line 87) | class NotNullConstraintViolationError < ConstraintViolationError
method initialize (line 90) | def initialize(message = "NOT NULL constraint has been violated")
class CheckConstraintViolationError (line 98) | class CheckConstraintViolationError < ConstraintViolationError
method initialize (line 101) | def initialize(message = "Check constraint has been violated")
class UnknownDatabaseTypeError (line 109) | class UnknownDatabaseTypeError < Error
class MissingPrimaryKeyError (line 115) | class MissingPrimaryKeyError < Error
class UnknownAttributeError (line 121) | class UnknownAttributeError < Error
class UnknownDatabaseAdapterError (line 127) | class UnknownDatabaseAdapterError < Error
method initialize (line 128) | def initialize(url)
FILE: lib/hanami/model/mapped_relation.rb
type Hanami (line 3) | module Hanami
type Model (line 4) | module Model
class MappedRelation (line 11) | class MappedRelation < SimpleDelegator
method mapper_name (line 24) | def self.mapper_name
method initialize (line 30) | def initialize(relation)
method [] (line 54) | def [](attribute)
FILE: lib/hanami/model/mapping.rb
type Hanami (line 5) | module Hanami
type Model (line 6) | module Model
class Mapping (line 11) | class Mapping
method initialize (line 18) | def initialize(&blk)
method t (line 26) | def t(name, *args)
method model (line 31) | def model(entity)
method register_as (line 35) | def register_as(name)
method attribute (line 39) | def attribute(name, options)
method process (line 47) | def process(input)
method reverse? (line 52) | def reverse?
method translate (line 57) | def translate(attribute)
FILE: lib/hanami/model/migration.rb
type Hanami (line 3) | module Hanami
type Model (line 4) | module Model
class Migration (line 9) | class Migration
method initialize (line 20) | def initialize(gateway, &block)
method run (line 28) | def run(direction = :up)
FILE: lib/hanami/model/migrator.rb
type Hanami (line 6) | module Hanami
type Model (line 7) | module Model
class MigrationError (line 11) | class MigrationError < Hanami::Model::Error
class Migrator (line 17) | class Migrator
method create (line 47) | def self.create
method drop (line 77) | def self.drop
method migrate (line 126) | def self.migrate(version: nil)
method rollback (line 162) | def self.rollback(steps: 1)
method apply (line 206) | def self.apply
method prepare (line 248) | def self.prepare
method version (line 267) | def self.version
method initialize (line 279) | def initialize(configuration: self.class.configuration)
method create (line 288) | def create
method drop (line 296) | def drop
method migrate (line 304) | def migrate(version: nil)
method rollback (line 312) | def rollback(steps: 1)
method apply (line 320) | def apply
method prepare (line 330) | def prepare
method version (line 343) | def version
method configuration (line 351) | def self.configuration
method migrations (line 373) | def migrations
method migrations? (line 381) | def migrations?
method delete_migrations (line 389) | def delete_migrations
FILE: lib/hanami/model/migrator/adapter.rb
type Hanami (line 7) | module Hanami
type Model (line 8) | module Model
class Migrator (line 9) | class Migrator
class Adapter (line 14) | class Adapter
method for (line 31) | def self.for(configuration)
method initialize (line 53) | def initialize(connection)
method create (line 64) | def create
method drop (line 75) | def drop
method migrate (line 81) | def migrate(migrations, version)
method rollback (line 91) | def rollback(migrations, steps)
method load (line 107) | def load
method version (line 115) | def version
method version_to_rollback (line 133) | def version_to_rollback(table, steps)
method migrations_table_dataset (line 142) | def migrations_table_dataset
method schema (line 152) | def schema
method new_connection (line 166) | def new_connection(global: false)
method database (line 174) | def database
method port (line 180) | def port
method host (line 186) | def host
method username (line 192) | def username
method password (line 198) | def password
method migrations_table (line 204) | def migrations_table
method escape (line 210) | def escape(string)
method execute (line 216) | def execute(command, env: {}, error: ->(err) { raise MigrationEr...
FILE: lib/hanami/model/migrator/connection.rb
type Hanami (line 5) | module Hanami
type Model (line 6) | module Model
class Migrator (line 7) | class Migrator
class Connection (line 14) | class Connection
method initialize (line 17) | def initialize(configuration)
method raw (line 23) | def raw
method host (line 40) | def host
method port (line 50) | def port
method database (line 60) | def database
method database_type (line 72) | def database_type
method user (line 89) | def user
method password (line 99) | def password
method uri (line 107) | def uri
method global_uri (line 115) | def global_uri
method jdbc? (line 123) | def jdbc?
method parsed_uri (line 131) | def parsed_uri
method schema (line 136) | def schema
method table (line 144) | def table(name)
method parsed_opt (line 160) | def parsed_opt(option, query: parsed_uri.query)
FILE: lib/hanami/model/migrator/logger.rb
type Hanami (line 5) | module Hanami
type Model (line 6) | module Model
class Migrator (line 7) | class Migrator
class Logger (line 12) | class Logger < Hanami::Logger
class Formatter (line 17) | class Formatter < Hanami::Logger::Formatter
method _format (line 22) | def _format(hash)
method initialize (line 29) | def initialize(stream)
FILE: lib/hanami/model/migrator/mysql_adapter.rb
type Hanami (line 3) | module Hanami
type Model (line 4) | module Model
class Migrator (line 5) | class Migrator
class MySQLAdapter (line 10) | class MySQLAdapter < Adapter
method create (line 27) | def create
method drop (line 41) | def drop
method dump (line 55) | def dump
method load (line 62) | def load
method password (line 70) | def password
method port (line 74) | def port
method dump_structure (line 80) | def dump_structure
method load_structure (line 86) | def load_structure
method dump_migrations_data (line 92) | def dump_migrations_data
FILE: lib/hanami/model/migrator/postgres_adapter.rb
type Hanami (line 5) | module Hanami
type Model (line 6) | module Model
class Migrator (line 7) | class Migrator
class PostgresAdapter (line 12) | class PostgresAdapter < Adapter
method create (line 37) | def create
method drop (line 43) | def drop
method dump (line 49) | def dump
method load (line 56) | def load
method environment_variables (line 64) | def environment_variables
method dump_structure (line 75) | def dump_structure
method load_structure (line 81) | def load_structure
method dump_migrations_data (line 89) | def dump_migrations_data
method call_db_command (line 96) | def call_db_command(command)
method modified_message (line 110) | def modified_message(original_message)
FILE: lib/hanami/model/migrator/sqlite_adapter.rb
type Hanami (line 7) | module Hanami
type Model (line 8) | module Model
class Migrator (line 9) | class Migrator
class SQLiteAdapter (line 14) | class SQLiteAdapter < Adapter
type Memory (line 19) | module Memory
function create (line 22) | def create
function drop (line 27) | def drop
method initialize (line 35) | def initialize(configuration)
method create (line 42) | def create
method drop (line 51) | def drop
method dump (line 59) | def dump
method load (line 66) | def load
method path (line 74) | def path
method root (line 82) | def root
method memory? (line 88) | def memory?
method dump_structure (line 96) | def dump_structure
method load_structure (line 102) | def load_structure
method dump_migrations_data (line 109) | def dump_migrations_data
FILE: lib/hanami/model/plugins.rb
type Hanami (line 3) | module Hanami
type Model (line 4) | module Model
type Plugins (line 9) | module Plugins
class WrappingInput (line 14) | class WrappingInput
method initialize (line 17) | def initialize(_relation, input)
FILE: lib/hanami/model/plugins/mapping.rb
type Hanami (line 3) | module Hanami
type Model (line 4) | module Model
type Plugins (line 5) | module Plugins
type Mapping (line 10) | module Mapping
class InputWithMapping (line 15) | class InputWithMapping < WrappingInput
method initialize (line 18) | def initialize(relation, input)
method [] (line 27) | def [](value)
type ClassMethods (line 36) | module ClassMethods
function build (line 41) | def build(relation, options = {})
function included (line 49) | def self.included(klass)
FILE: lib/hanami/model/plugins/schema.rb
type Hanami (line 3) | module Hanami
type Model (line 4) | module Model
type Plugins (line 5) | module Plugins
type Schema (line 10) | module Schema
class InputWithSchema (line 15) | class InputWithSchema < WrappingInput
method initialize (line 18) | def initialize(relation, input)
method [] (line 27) | def [](value)
type ClassMethods (line 36) | module ClassMethods
function build (line 41) | def build(relation, options = {})
function included (line 49) | def self.included(klass)
FILE: lib/hanami/model/plugins/timestamps.rb
type Hanami (line 3) | module Hanami
type Model (line 4) | module Model
type Plugins (line 5) | module Plugins
type Timestamps (line 10) | module Timestamps
class InputWithTimestamp (line 17) | class InputWithTimestamp < WrappingInput
method initialize (line 26) | def initialize(relation, input)
method [] (line 35) | def [](value)
method _touch (line 45) | def _touch(_value)
method timestamps? (line 53) | def timestamps?
class InputWithUpdateTimestamp (line 62) | class InputWithUpdateTimestamp < InputWithTimestamp
method _touch (line 67) | def _touch(value, now)
class InputWithCreateTimestamp (line 77) | class InputWithCreateTimestamp < InputWithUpdateTimestamp
method _touch (line 82) | def _touch(value, now)
type ClassMethods (line 93) | module ClassMethods
function build (line 98) | def build(relation, options = {})
function included (line 112) | def self.included(klass)
FILE: lib/hanami/model/relation_name.rb
type Hanami (line 6) | module Hanami
type Model (line 7) | module Model
class RelationName (line 15) | class RelationName < EntityName
method new (line 21) | def self.new(name)
FILE: lib/hanami/model/sql.rb
type Hanami (line 6) | module Hanami
type Model (line 8) | module Model
function migration (line 50) | def self.migration(&blk)
type Sql (line 57) | module Sql
function function (line 85) | def self.function(name)
function literal (line 119) | def self.literal(string)
function asc (line 129) | def self.asc(column)
function desc (line 139) | def self.desc(column)
FILE: lib/hanami/model/sql/console.rb
type Hanami (line 5) | module Hanami
type Model (line 6) | module Model
type Sql (line 7) | module Sql
class Console (line 12) | class Console
method initialize (line 21) | def initialize(uri)
method console (line 29) | def console
FILE: lib/hanami/model/sql/consoles/abstract.rb
type Hanami (line 3) | module Hanami
type Model (line 4) | module Model
type Sql (line 5) | module Sql
type Consoles (line 6) | module Consoles
class Abstract (line 11) | class Abstract
method initialize (line 14) | def initialize(uri)
method database_name (line 22) | def database_name
method concat (line 28) | def concat(*tokens)
FILE: lib/hanami/model/sql/consoles/mysql.rb
type Hanami (line 5) | module Hanami
type Model (line 6) | module Model
type Sql (line 7) | module Sql
type Consoles (line 8) | module Consoles
class Mysql (line 13) | class Mysql < Abstract
method connection_string (line 20) | def connection_string
method command (line 28) | def command
method host (line 34) | def host
method database (line 40) | def database
method port (line 46) | def port
method username (line 52) | def username
method password (line 58) | def password
FILE: lib/hanami/model/sql/consoles/postgresql.rb
type Hanami (line 6) | module Hanami
type Model (line 7) | module Model
type Sql (line 8) | module Sql
type Consoles (line 9) | module Consoles
class Postgresql (line 14) | class Postgresql < Abstract
method connection_string (line 25) | def connection_string
method command (line 34) | def command
method host (line 40) | def host
method database (line 46) | def database
method port (line 52) | def port
method username (line 59) | def username
method configure_password (line 66) | def configure_password
method query (line 73) | def query
FILE: lib/hanami/model/sql/consoles/sqlite.rb
type Hanami (line 6) | module Hanami
type Model (line 7) | module Model
type Sql (line 8) | module Sql
type Consoles (line 9) | module Consoles
class Sqlite (line 14) | class Sqlite < Abstract
method connection_string (line 21) | def connection_string
method command (line 29) | def command
method host (line 35) | def host
method database (line 41) | def database
FILE: lib/hanami/model/sql/entity/schema.rb
type Hanami (line 7) | module Hanami
type Model (line 8) | module Model
type Sql (line 9) | module Sql
type Entity (line 10) | module Entity
class Schema (line 22) | class Schema < Hanami::Entity::Schema
method initialize (line 36) | def initialize(registry, relation, mapping)
method call (line 51) | def call(attributes)
method attribute? (line 67) | def attribute?(name)
method build (line 89) | def build(registry, relation, mapping)
method build_attributes (line 106) | def build_attributes(relation, mapping)
method build_associations (line 125) | def build_associations(registry, associations)
method coercible (line 136) | def coercible(type)
FILE: lib/hanami/model/sql/types.rb
type Hanami (line 6) | module Hanami
type Model (line 7) | module Model
type Sql (line 8) | module Sql
type Types (line 12) | module Types
type Schema (line 18) | module Schema
function coercible (line 67) | def self.coercible(attribute)
function pg_json_pristines (line 89) | def self.pg_json_pristines
function pg_json? (line 97) | def self.pg_json?(pristine)
class AssociationType (line 108) | class AssociationType < Hanami::Model::Types::Schema::Coercibl...
method valid? (line 117) | def valid?(value)
method success (line 123) | def success(*args)
FILE: lib/hanami/model/sql/types/schema/coercions.rb
type Hanami (line 6) | module Hanami
type Model (line 7) | module Model
type Sql (line 8) | module Sql
type Types (line 9) | module Types
type Schema (line 10) | module Schema
type Coercions (line 17) | module Coercions
function int (line 28) | def self.int(arg)
function float (line 49) | def self.float(arg)
function decimal (line 70) | def self.decimal(arg)
function date (line 93) | def self.date(arg)
function datetime (line 116) | def self.datetime(arg)
function time (line 139) | def self.time(arg)
function array (line 164) | def self.array(arg)
function hash (line 185) | def self.hash(arg)
function pg_json (line 208) | def self.pg_json(arg)
FILE: lib/hanami/model/types.rb
type Hanami (line 5) | module Hanami
type Model (line 6) | module Model
type Types (line 10) | module Types
function included (line 15) | def self.included(mod)
type ClassMethods (line 22) | module ClassMethods
function Entity (line 46) | def Entity(type)
function Collection (line 76) | def Collection(type)
type Schema (line 85) | module Schema
class CoercibleType (line 90) | class CoercibleType < Dry::Types::Definition
method call (line 101) | def call(value)
method valid? (line 122) | def valid?(value)
method coerce (line 132) | def coerce(value)
method object (line 143) | def object
FILE: lib/hanami/model/version.rb
type Hanami (line 3) | module Hanami
type Model (line 4) | module Model
FILE: lib/hanami/repository.rb
type Hanami (line 13) | module Hanami
class Repository (line 112) | class Repository < ROM::Repository::Root
method configuration (line 125) | def self.configuration
method container (line 133) | def self.container
method command (line 157) | def command(*args, **opts, &block)
method define_relation (line 171) | def self.define_relation
method define_mapping (line 201) | def self.define_mapping
method define_associations (line 224) | def self.define_associations
method associations (line 241) | def self.associations(&blk)
method schema (line 262) | def self.schema(&blk)
method mapping (line 281) | def self.mapping(&blk)
method load! (line 289) | def self.load!
method inherited (line 298) | def self.inherited(klass)
type Commands (line 330) | module Commands
function create (line 348) | def create(*args)
function update (line 377) | def update(*args)
function delete (line 396) | def delete(*args)
method initialize (line 408) | def initialize
method find (line 426) | def find(id)
method all (line 440) | def all
method first (line 452) | def first
method last (line 464) | def last
method clear (line 474) | def clear
method assoc (line 486) | def assoc(target, subject = nil)
FILE: spec/support/database.rb
type Database (line 3) | module Database
class Setup (line 4) | class Setup
method initialize (line 7) | def initialize(adapter: ENV["DB"])
method run (line 11) | def run
type Strategies (line 16) | module Strategies
function strategies (line 21) | def self.strategies
class Strategy (line 28) | class Strategy
method for (line 30) | def for(adapter)
method strategies (line 38) | def strategies
function engine (line 44) | def self.engine
function engine? (line 48) | def self.engine?(name)
FILE: spec/support/database/strategies/abstract.rb
type Database (line 3) | module Database
type Strategies (line 4) | module Strategies
class Abstract (line 5) | class Abstract
method eligible? (line 6) | def self.eligible?(_adapter)
method run (line 10) | def run
method before (line 22) | def before
method database_name (line 26) | def database_name
method load_dependencies (line 30) | def load_dependencies
method export_env (line 34) | def export_env
method create_database (line 38) | def create_database
method configure (line 42) | def configure
method after (line 50) | def after
method jruby? (line 56) | def jruby?
method ci? (line 60) | def ci?
FILE: spec/support/database/strategies/mysql.rb
type Database (line 5) | module Database
type Strategies (line 6) | module Strategies
class Mysql (line 7) | class Mysql < Sql
type JrubyImplementation (line 8) | module JrubyImplementation
function load_dependencies (line 11) | def load_dependencies
function export_env (line 16) | def export_env
function host (line 21) | def host
function credentials (line 25) | def credentials
type TravisCiImplementation (line 36) | module TravisCiImplementation
function export_env (line 39) | def export_env
function create_database (line 45) | def create_database
function run_command (line 53) | def run_command(command)
type CircleCiImplementation (line 59) | module CircleCiImplementation
function export_env (line 62) | def export_env
function create_database (line 68) | def create_database
function run_command (line 75) | def run_command(command)
method eligible? (line 81) | def self.eligible?(adapter)
method initialize (line 85) | def initialize
method load_dependencies (line 98) | def load_dependencies
method export_env (line 103) | def export_env
method create_database (line 111) | def create_database
method run_command (line 119) | def run_command(command)
FILE: spec/support/database/strategies/postgresql.rb
type Database (line 5) | module Database
type Strategies (line 6) | module Strategies
class Postgresql (line 7) | class Postgresql < Sql
type JrubyImplementation (line 8) | module JrubyImplementation
function load_dependencies (line 11) | def load_dependencies
function export_env (line 18) | def export_env
type TravisCiImplementation (line 24) | module TravisCiImplementation
function export_env (line 27) | def export_env
type CircleCiImplementation (line 33) | module CircleCiImplementation
function create_database (line 36) | def create_database
type GithubActionsImplementation (line 47) | module GithubActionsImplementation
function export_env (line 50) | def export_env
function create_database (line 56) | def create_database
method eligible? (line 67) | def self.eligible?(adapter)
method initialize (line 71) | def initialize
method load_dependencies (line 85) | def load_dependencies
method create_database (line 90) | def create_database
method export_env (line 106) | def export_env
method try (line 115) | def try(message)
FILE: spec/support/database/strategies/sql.rb
type Database (line 8) | module Database
type Strategies (line 9) | module Strategies
class Sql (line 10) | class Sql < Abstract
method eligible? (line 11) | def self.eligible?(_adapter)
method before (line 17) | def before
method export_env (line 23) | def export_env
method configure (line 29) | def configure
method after (line 44) | def after
method migrate (line 50) | def migrate
method credentials (line 57) | def credentials
method host (line 63) | def host
method host_and_credentials (line 67) | def host_and_credentials
method logger (line 73) | def logger
FILE: spec/support/database/strategies/sqlite.rb
type Database (line 6) | module Database
type Strategies (line 7) | module Strategies
class Sqlite (line 8) | class Sqlite < Sql
type JrubyImplementation (line 9) | module JrubyImplementation
function load_dependencies (line 12) | def load_dependencies
function export_env (line 18) | def export_env
type CiImplementation (line 24) | module CiImplementation
method eligible? (line 27) | def self.eligible?(adapter)
method initialize (line 31) | def initialize
method database_name (line 38) | def database_name
method load_dependencies (line 42) | def load_dependencies
method create_database (line 47) | def create_database
method export_env (line 54) | def export_env
FILE: spec/support/fixtures.rb
class BaseParams (line 5) | class BaseParams < OpenStruct
method to_hash (line 6) | def to_hash
class User (line 11) | class User < Hanami::Entity
class Avatar (line 14) | class Avatar < Hanami::Entity
class Author (line 17) | class Author < Hanami::Entity
class Book (line 20) | class Book < Hanami::Entity
class Category (line 23) | class Category < Hanami::Entity
class BookOntology (line 26) | class BookOntology < Hanami::Entity
class Operator (line 29) | class Operator < Hanami::Entity
class AccessToken (line 32) | class AccessToken < Hanami::Entity
class SourceFile (line 35) | class SourceFile < Hanami::Entity
class Post (line 38) | class Post < Hanami::Entity
class Comment (line 41) | class Comment < Hanami::Entity
class Warehouse (line 44) | class Warehouse < Hanami::Entity
class Account (line 52) | class Account < Hanami::Entity
class PageVisit (line 64) | class PageVisit < Hanami::Entity
class Person (line 78) | class Person < Hanami::Entity
class Product (line 85) | class Product < Hanami::Entity
class Color (line 88) | class Color < Hanami::Entity
class Label (line 91) | class Label < Hanami::Entity
class PostRepository (line 94) | class PostRepository < Hanami::Repository
method find_with_commenters (line 101) | def find_with_commenters(id)
method commenters_for (line 105) | def commenters_for(post)
method find_with_author (line 109) | def find_with_author(id)
method feed_for (line 113) | def feed_for(id)
method author_for (line 117) | def author_for(post)
class CommentRepository (line 122) | class CommentRepository < Hanami::Repository
method commenter_for (line 128) | def commenter_for(comment)
class AvatarRepository (line 133) | class AvatarRepository < Hanami::Repository
method by_user (line 138) | def by_user(id)
class UserRepository (line 143) | class UserRepository < Hanami::Repository
method find_with_threads (line 150) | def find_with_threads(id)
method threads_for (line 154) | def threads_for(user)
method find_with_avatar (line 158) | def find_with_avatar(id)
method create_with_avatar (line 162) | def create_with_avatar(data)
method remove_avatar (line 166) | def remove_avatar(user)
method add_avatar (line 170) | def add_avatar(user, data)
method update_avatar (line 174) | def update_avatar(user, data)
method replace_avatar (line 178) | def replace_avatar(user, data)
method avatar_for (line 182) | def avatar_for(user)
method by_name (line 186) | def by_name(name)
method by_matching_name (line 190) | def by_matching_name(name)
method by_name_with_root (line 194) | def by_name_with_root(name)
method find_all_by_manual_query (line 198) | def find_all_by_manual_query
method ids (line 202) | def ids
method select_id_and_name (line 206) | def select_id_and_name
class AvatarRepository (line 211) | class AvatarRepository < Hanami::Repository
method by_user (line 138) | def by_user(id)
class AuthorRepository (line 214) | class AuthorRepository < Hanami::Repository
method create_many (line 219) | def create_many(data, opts: {})
method create_with_books (line 223) | def create_with_books(data)
method find_with_books (line 227) | def find_with_books(id)
method books_for (line 231) | def books_for(author)
method add_book (line 235) | def add_book(author, data)
method remove_book (line 239) | def remove_book(author, id)
method delete_books (line 243) | def delete_books(author)
method delete_on_sales_books (line 247) | def delete_on_sales_books(author)
method books_count (line 251) | def books_count(author)
method on_sales_books_count (line 255) | def on_sales_books_count(author)
method find_book (line 259) | def find_book(author, id)
method book_exists? (line 263) | def book_exists?(author, id)
method book_for (line 269) | def book_for(author, id)
class BookOntologyRepository (line 274) | class BookOntologyRepository < Hanami::Repository
class CategoryRepository (line 281) | class CategoryRepository < Hanami::Repository
method books_for (line 286) | def books_for(category)
method on_sales_books_count (line 290) | def on_sales_books_count(category)
method books_count (line 294) | def books_count(category)
method find_with_books (line 298) | def find_with_books(id)
method add_books (line 302) | def add_books(category, *books)
method remove_book (line 306) | def remove_book(category, book_id)
class BookRepository (line 311) | class BookRepository < Hanami::Repository
method add_category (line 317) | def add_category(book, category)
method clear_categories (line 321) | def clear_categories(book)
method categories_for (line 325) | def categories_for(book)
method find_with_categories (line 329) | def find_with_categories(id)
method find_with_author (line 333) | def find_with_author(id)
method author_for (line 337) | def author_for(book)
class OperatorRepository (line 342) | class OperatorRepository < Hanami::Repository
class AccessTokenRepository (line 351) | class AccessTokenRepository < Hanami::Repository
class SourceFileRepository (line 355) | class SourceFileRepository < Hanami::Repository
class WarehouseRepository (line 358) | class WarehouseRepository < Hanami::Repository
class ProductRepository (line 361) | class ProductRepository < Hanami::Repository
class ColorRepository (line 364) | class ColorRepository < Hanami::Repository
class LabelRepository (line 373) | class LabelRepository < Hanami::Repository
FILE: spec/support/platform.rb
type Platform (line 3) | module Platform
function ci? (line 10) | def self.ci?
function match (line 14) | def self.match(&blk)
function match? (line 18) | def self.match?(**args)
type PlatformHelpers (line 23) | module PlatformHelpers
function with_platform (line 24) | def with_platform(**args)
function unless_platform (line 28) | def unless_platform(**args)
FILE: spec/support/platform/ci.rb
type Platform (line 3) | module Platform
type Ci (line 4) | module Ci
function ci? (line 5) | def self.ci?(name)
function current (line 9) | def self.current
function travis? (line 20) | def travis?
function circle? (line 24) | def circle?
function drone? (line 28) | def drone?
function github? (line 32) | def github?
FILE: spec/support/platform/db.rb
type Platform (line 3) | module Platform
type Db (line 4) | module Db
function db? (line 5) | def self.db?(name)
function current (line 9) | def self.current
FILE: spec/support/platform/engine.rb
type Platform (line 5) | module Platform
type Engine (line 6) | module Engine
function engine? (line 7) | def self.engine?(name)
function current (line 11) | def self.current
function ruby? (line 20) | def ruby?
function jruby? (line 24) | def jruby?
FILE: spec/support/platform/matcher.rb
type Platform (line 5) | module Platform
class Matcher (line 6) | class Matcher
class Nope (line 7) | class Nope < Hanami::Utils::BasicObject
method or (line 8) | def or(other, &blk)
method method_missing (line 14) | def method_missing(*)
method match (line 21) | def self.match(&blk)
method match? (line 27) | def self.match?(os: Os.current, ci: Ci.current, engine: Engine.curre...
method initialize (line 33) | def initialize
method os (line 37) | def os(name, &blk)
method ci (line 43) | def ci(name, &blk)
method engine (line 49) | def engine(name, &blk)
method db (line 55) | def db(name, &blk)
method default (line 61) | def default(&blk)
method match (line 67) | def match(&blk)
method nope (line 71) | def nope
method yep (line 75) | def yep
method resolve (line 79) | def resolve
method os? (line 83) | def os?(name)
method ci? (line 87) | def ci?(name)
method engine? (line 91) | def engine?(name)
method db? (line 95) | def db?(name)
FILE: spec/support/platform/os.rb
type Platform (line 5) | module Platform
type Os (line 6) | module Os
function os? (line 7) | def self.os?(name)
function current (line 11) | def self.current
FILE: spec/support/test_io.rb
type TestIO (line 3) | module TestIO
function with_stdout (line 4) | def self.with_stdout
function stream (line 13) | def self.stream
FILE: spec/unit/hanami/entity/automatic_schema_spec.rb
function to_hash (line 9) | def to_hash
FILE: spec/unit/hanami/entity/manual_schema/base_spec.rb
function to_hash (line 9) | def to_hash
FILE: spec/unit/hanami/entity/manual_schema/strict_spec.rb
function to_hash (line 9) | def to_hash
FILE: spec/unit/hanami/entity/schemaless_spec.rb
function to_hash (line 11) | def to_hash
FILE: spec/unit/hanami/model/migrator/mysql.rb
function prepare_migrations_directory (line 457) | def prepare_migrations_directory
function clean_migrations (line 462) | def clean_migrations
FILE: spec/unit/hanami/model/migrator/postgresql.rb
function prepare_migrations_directory (line 535) | def prepare_migrations_directory
function clean_migrations (line 540) | def clean_migrations
FILE: spec/unit/hanami/model/migrator/sqlite.rb
function prepare_migrations_directory (line 409) | def prepare_migrations_directory
function clean_migrations (line 414) | def clean_migrations
FILE: spec/unit/hanami/model/sql/schema/array_spec.rb
function to_ary (line 8) | def to_ary
FILE: spec/unit/hanami/model/sql/schema/date_spec.rb
function to_date (line 8) | def to_date
FILE: spec/unit/hanami/model/sql/schema/date_time_spec.rb
function to_datetime (line 8) | def to_datetime
FILE: spec/unit/hanami/model/sql/schema/decimal_spec.rb
function to_d (line 8) | def to_d
FILE: spec/unit/hanami/model/sql/schema/float_spec.rb
function to_f (line 8) | def to_f
FILE: spec/unit/hanami/model/sql/schema/hash_spec.rb
function to_hash (line 8) | def to_hash
FILE: spec/unit/hanami/model/sql/schema/int_spec.rb
function to_int (line 8) | def to_int
FILE: spec/unit/hanami/model/sql/schema/time_spec.rb
function to_time (line 8) | def to_time
Condensed preview — 150 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (455K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 15,
"preview": "github: hanami\n"
},
{
"path": ".github/workflows/ci.yml",
"chars": 1812,
"preview": "name: ci\n\n\"on\":\n push:\n paths:\n - \".github/workflows/ci.yml\"\n - \"lib/**\"\n - \"*.gemspec\"\n - \"spec"
},
{
"path": ".gitignore",
"chars": 199,
"preview": "*.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\nInst"
},
{
"path": ".jrubyrc",
"chars": 21,
"preview": "debug.fullTrace=true\n"
},
{
"path": ".rspec",
"chars": 30,
"preview": "--color\n--require spec_helper\n"
},
{
"path": ".rubocop.yml",
"chars": 438,
"preview": "# Please keep AllCops, Bundler, Style, Metrics groups and then order cops\n# alphabetically\ninherit_from:\n - https://raw"
},
{
"path": ".yardopts",
"chars": 47,
"preview": "--protected\n--private\n-\nLICENSE.md\nlib/**/*.rb\n"
},
{
"path": "CHANGELOG.md",
"chars": 14704,
"preview": "# Hanami::Model\nA persistence layer for Hanami\n\n## v1.3.3 - 2021-05-22\n### Fixed\n- [Sean Collins] Specify dependency on "
},
{
"path": "Gemfile",
"chars": 786,
"preview": "# frozen_string_literal: true\n\nsource \"https://rubygems.org\"\ngemspec\n\nunless ENV[\"CI\"]\n gem \"byebug\", require: false, p"
},
{
"path": "LICENSE.md",
"chars": 1070,
"preview": "Copyright © 2014-2021 Luca Guidi\n\nMIT License\n\nPermission is hereby granted, free of charge, to any person obtaining\na c"
},
{
"path": "README.md",
"chars": 7849,
"preview": "# Hanami::Model (deprecated)\n\n## _Important notice_\n**NOTE**: Hanami::Model was the persistence layer for Hanami 1.x. Th"
},
{
"path": "Rakefile",
"chars": 293,
"preview": "# frozen_string_literal: true\n\nrequire \"rake\"\nrequire \"bundler/gem_tasks\"\nrequire \"rspec/core/rake_task\"\nrequire \"hanami"
},
{
"path": "hanami-model.gemspec",
"chars": 1692,
"preview": "# frozen_string_literal: true\n\nlib = File.expand_path(\"../lib\", __FILE__)\n$LOAD_PATH.unshift(lib) unless $LOAD_PATH.incl"
},
{
"path": "lib/hanami/entity/schema.rb",
"chars": 7170,
"preview": "# frozen_string_literal: true\n\nrequire \"hanami/model/types\"\nrequire \"hanami/utils/hash\"\n\nmodule Hanami\n class Entity\n "
},
{
"path": "lib/hanami/entity.rb",
"chars": 5475,
"preview": "# frozen_string_literal: true\n\nrequire \"hanami/model/types\"\n\nmodule Hanami\n # An object that is defined by its identity"
},
{
"path": "lib/hanami/model/association.rb",
"chars": 1236,
"preview": "# frozen_string_literal: true\n\nrequire \"rom-sql\"\nrequire \"hanami/model/associations/belongs_to\"\nrequire \"hanami/model/as"
},
{
"path": "lib/hanami/model/associations/belongs_to.rb",
"chars": 2458,
"preview": "# frozen_string_literal: true\n\nrequire \"hanami/model/types\"\n\nmodule Hanami\n module Model\n module Associations\n "
},
{
"path": "lib/hanami/model/associations/dsl.rb",
"chars": 1016,
"preview": "# frozen_string_literal: true\n\nmodule Hanami\n module Model\n module Associations\n # Auto-infer relations linked "
},
{
"path": "lib/hanami/model/associations/has_many.rb",
"chars": 4910,
"preview": "# frozen_string_literal: true\n\nrequire \"hanami/model/types\"\n\nmodule Hanami\n module Model\n module Associations\n "
},
{
"path": "lib/hanami/model/associations/has_one.rb",
"chars": 3940,
"preview": "# frozen_string_literal: true\n\nrequire \"hanami/utils/hash\"\n\nmodule Hanami\n module Model\n module Associations\n #"
},
{
"path": "lib/hanami/model/associations/many_to_many.rb",
"chars": 4917,
"preview": "# frozen_string_literal: true\n\nrequire \"hanami/utils/hash\"\n\nmodule Hanami\n module Model\n module Associations\n #"
},
{
"path": "lib/hanami/model/configuration.rb",
"chars": 4780,
"preview": "# frozen_string_literal: true\n\nrequire \"rom/configuration\"\n\nmodule Hanami\n module Model\n # Configuration for the fra"
},
{
"path": "lib/hanami/model/configurator.rb",
"chars": 1809,
"preview": "# frozen_string_literal: true\n\nmodule Hanami\n module Model\n # Configuration DSL\n #\n # @since 0.7.0\n # @api "
},
{
"path": "lib/hanami/model/entity_name.rb",
"chars": 811,
"preview": "# frozen_string_literal: true\n\nmodule Hanami\n module Model\n # Conventional name for entities.\n #\n # Given a re"
},
{
"path": "lib/hanami/model/error.rb",
"chars": 3025,
"preview": "# frozen_string_literal: true\n\nrequire \"concurrent\"\n\nmodule Hanami\n module Model\n # Default Error class\n #\n # "
},
{
"path": "lib/hanami/model/mapped_relation.rb",
"chars": 1552,
"preview": "# frozen_string_literal: true\n\nmodule Hanami\n module Model\n # Mapped proxy for ROM relations.\n #\n # It elimina"
},
{
"path": "lib/hanami/model/mapping.rb",
"chars": 1147,
"preview": "# frozen_string_literal: true\n\nrequire \"transproc/all\"\n\nmodule Hanami\n module Model\n # Mapping\n #\n # @since 0."
},
{
"path": "lib/hanami/model/migration.rb",
"chars": 627,
"preview": "# frozen_string_literal: true\n\nmodule Hanami\n module Model\n # Database migration\n #\n # @since 0.7.0\n # @api"
},
{
"path": "lib/hanami/model/migrator/adapter.rb",
"chars": 6172,
"preview": "# frozen_string_literal: true\n\nrequire \"uri\"\nrequire \"shellwords\"\nrequire \"open3\"\n\nmodule Hanami\n module Model\n clas"
},
{
"path": "lib/hanami/model/migrator/connection.rb",
"chars": 4261,
"preview": "# frozen_string_literal: true\n\nrequire \"cgi\"\n\nmodule Hanami\n module Model\n class Migrator\n # Sequel connection "
},
{
"path": "lib/hanami/model/migrator/logger.rb",
"chars": 760,
"preview": "# frozen_string_literal: true\n\nrequire \"hanami/logger\"\n\nmodule Hanami\n module Model\n class Migrator\n # Automati"
},
{
"path": "lib/hanami/model/migrator/mysql_adapter.rb",
"chars": 2782,
"preview": "# frozen_string_literal: true\n\nmodule Hanami\n module Model\n class Migrator\n # MySQL adapter\n #\n # @si"
},
{
"path": "lib/hanami/model/migrator/postgres_adapter.rb",
"chars": 3422,
"preview": "# frozen_string_literal: true\n\nrequire \"hanami/utils/blank\"\n\nmodule Hanami\n module Model\n class Migrator\n # Pos"
},
{
"path": "lib/hanami/model/migrator/sqlite_adapter.rb",
"chars": 2944,
"preview": "# frozen_string_literal: true\n\nrequire \"pathname\"\nrequire \"hanami/utils\"\nrequire \"English\"\n\nmodule Hanami\n module Model"
},
{
"path": "lib/hanami/model/migrator.rb",
"chars": 10732,
"preview": "# frozen_string_literal: true\n\nrequire \"sequel\"\nrequire \"sequel/extensions/migration\"\n\nmodule Hanami\n module Model\n "
},
{
"path": "lib/hanami/model/plugins/mapping.rb",
"chars": 1382,
"preview": "# frozen_string_literal: true\n\nmodule Hanami\n module Model\n module Plugins\n # Transform output into model domai"
},
{
"path": "lib/hanami/model/plugins/schema.rb",
"chars": 1350,
"preview": "# frozen_string_literal: true\n\nmodule Hanami\n module Model\n module Plugins\n # Transform input values into datab"
},
{
"path": "lib/hanami/model/plugins/timestamps.rb",
"chars": 3096,
"preview": "# frozen_string_literal: true\n\nmodule Hanami\n module Model\n module Plugins\n # Automatically set/update timestam"
},
{
"path": "lib/hanami/model/plugins.rb",
"chars": 587,
"preview": "# frozen_string_literal: true\n\nmodule Hanami\n module Model\n # Plugins to extend read/write operations from/to the da"
},
{
"path": "lib/hanami/model/relation_name.rb",
"chars": 638,
"preview": "# frozen_string_literal: true\n\nrequire_relative \"entity_name\"\nrequire \"hanami/utils/string\"\n\nmodule Hanami\n module Mode"
},
{
"path": "lib/hanami/model/sql/console.rb",
"chars": 989,
"preview": "# frozen_string_literal: true\n\nrequire \"uri\"\n\nmodule Hanami\n module Model\n module Sql\n # SQL console\n #\n "
},
{
"path": "lib/hanami/model/sql/consoles/abstract.rb",
"chars": 628,
"preview": "# frozen_string_literal: true\n\nmodule Hanami\n module Model\n module Sql\n module Consoles\n # Abstract adap"
},
{
"path": "lib/hanami/model/sql/consoles/mysql.rb",
"chars": 1313,
"preview": "# frozen_string_literal: true\n\nrequire_relative \"abstract\"\n\nmodule Hanami\n module Model\n module Sql\n module Con"
},
{
"path": "lib/hanami/model/sql/consoles/postgresql.rb",
"chars": 1885,
"preview": "# frozen_string_literal: true\n\nrequire_relative \"abstract\"\nrequire \"cgi\"\n\nmodule Hanami\n module Model\n module Sql\n "
},
{
"path": "lib/hanami/model/sql/consoles/sqlite.rb",
"chars": 909,
"preview": "# frozen_string_literal: true\n\nrequire_relative \"abstract\"\nrequire \"shellwords\"\n\nmodule Hanami\n module Model\n module"
},
{
"path": "lib/hanami/model/sql/entity/schema.rb",
"chars": 4733,
"preview": "# frozen_string_literal: true\n\nrequire \"hanami/entity/schema\"\nrequire \"hanami/model/types\"\nrequire \"hanami/model/associa"
},
{
"path": "lib/hanami/model/sql/types/schema/coercions.rb",
"chars": 7233,
"preview": "# frozen_string_literal: true\n\nrequire \"hanami/utils/string\"\nrequire \"hanami/utils/hash\"\n\nmodule Hanami\n module Model\n "
},
{
"path": "lib/hanami/model/sql/types.rb",
"chars": 4772,
"preview": "# frozen_string_literal: true\n\nrequire \"hanami/model/types\"\nrequire \"rom/types\"\n\nmodule Hanami\n module Model\n module"
},
{
"path": "lib/hanami/model/sql.rb",
"chars": 4935,
"preview": "# frozen_string_literal: true\n\nrequire \"rom-sql\"\nrequire \"hanami/utils\"\n\nmodule Hanami\n # Hanami::Model migrations\n mo"
},
{
"path": "lib/hanami/model/types.rb",
"chars": 4092,
"preview": "# frozen_string_literal: true\n\nrequire \"rom/types\"\n\nmodule Hanami\n module Model\n # Types definitions\n #\n # @si"
},
{
"path": "lib/hanami/model/version.rb",
"chars": 143,
"preview": "# frozen_string_literal: true\n\nmodule Hanami\n module Model\n # Defines the version\n #\n # @since 0.1.0\n VERSI"
},
{
"path": "lib/hanami/model.rb",
"chars": 2268,
"preview": "# frozen_string_literal: true\n\nrequire \"rom\"\nrequire \"concurrent\"\nrequire \"hanami/entity\"\nrequire \"hanami/repository\"\n\n#"
},
{
"path": "lib/hanami/repository.rb",
"chars": 12993,
"preview": "# frozen_string_literal: true\n\nrequire \"rom-repository\"\nrequire \"hanami/model/entity_name\"\nrequire \"hanami/model/relatio"
},
{
"path": "lib/hanami-model.rb",
"chars": 54,
"preview": "# frozen_string_literal: true\n\nrequire \"hanami/model\"\n"
},
{
"path": "script/ci",
"chars": 471,
"preview": "#!/bin/bash\nset -euo pipefail\nIFS=$'\\n\\t'\n\nprepare_build() {\n if [ -d coverage ]; then\n rm -rf coverage\n fi\n}\n\nprin"
},
{
"path": "spec/integration/hanami/model/associations/belongs_to_spec.rb",
"chars": 1171,
"preview": "# frozen_string_literal: true\n\nRSpec.describe \"Associations (belongs_to)\" do\n it \"returns nil if association wasn't pre"
},
{
"path": "spec/integration/hanami/model/associations/has_many_spec.rb",
"chars": 6135,
"preview": "# frozen_string_literal: true\n\nRSpec.describe \"Associations (has_many)\" do\n let(:authors) { AuthorRepository.new }\n le"
},
{
"path": "spec/integration/hanami/model/associations/has_one_spec.rb",
"chars": 6162,
"preview": "# frozen_string_literal: true\n\nrequire \"spec_helper\"\n\nRSpec.describe \"Associations (has_one)\" do\n extend PlatformHelper"
},
{
"path": "spec/integration/hanami/model/associations/many_to_many_spec.rb",
"chars": 4661,
"preview": "# frozen_string_literal: true\n\nRSpec.describe \"Associations (has_many :through)\" do\n #### REPOS\n let(:books) { BookRep"
},
{
"path": "spec/integration/hanami/model/associations/relation_alias_spec.rb",
"chars": 2189,
"preview": "# frozen_string_literal: true\n\nRSpec.describe \"Alias (:as) support for associations\" do\n let(:users) { UserRepository."
},
{
"path": "spec/integration/hanami/model/migration/mysql.rb",
"chars": 16568,
"preview": "# frozen_string_literal: true\n\nRSpec.shared_examples \"migration_integration_mysql\" do\n before do\n @schema = Pathname"
},
{
"path": "spec/integration/hanami/model/migration/postgresql.rb",
"chars": 22759,
"preview": "# frozen_string_literal: true\n\nRSpec.shared_examples \"migration_integration_postgresql\" do\n before do\n @schema = Pat"
},
{
"path": "spec/integration/hanami/model/migration/sqlite.rb",
"chars": 17047,
"preview": "# frozen_string_literal: true\n\nRSpec.shared_examples \"migration_integration_sqlite\" do\n before do\n @schema = Pathnam"
},
{
"path": "spec/integration/hanami/model/migration_spec.rb",
"chars": 195,
"preview": "# frozen_string_literal: true\n\nrequire_relative \"./migration/#{Database.engine}.rb\"\n\nRSpec.describe \"Hanami::Model.migra"
},
{
"path": "spec/integration/hanami/model/repository/base_spec.rb",
"chars": 28250,
"preview": "# frozen_string_literal: true\n\nrequire \"securerandom\"\n\nRSpec.describe \"Repository (base)\" do\n extend PlatformHelpers\n\n "
},
{
"path": "spec/integration/hanami/model/repository/command_spec.rb",
"chars": 745,
"preview": "# frozen_string_literal: true\n\nRSpec.describe \"Customized commands\" do\n subject(:authors) { AuthorRepository.new }\n\n l"
},
{
"path": "spec/integration/hanami/model/repository/legacy_spec.rb",
"chars": 3070,
"preview": "# frozen_string_literal: true\n\nRSpec.describe \"Repository (legacy)\" do\n describe \"#find\" do\n it \"finds record by pri"
},
{
"path": "spec/spec_helper.rb",
"chars": 297,
"preview": "# frozen_string_literal: true\n\n$LOAD_PATH.unshift \"lib\"\nrequire \"hanami/devtools/unit\"\nrequire \"hanami/model\"\n\nrequire_r"
},
{
"path": "spec/support/database/strategies/abstract.rb",
"chars": 1145,
"preview": "# frozen_string_literal: true\n\nmodule Database\n module Strategies\n class Abstract\n def self.eligible?(_adapter)"
},
{
"path": "spec/support/database/strategies/mysql.rb",
"chars": 3506,
"preview": "# frozen_string_literal: true\n\nrequire_relative \"sql\"\n\nmodule Database\n module Strategies\n class Mysql < Sql\n m"
},
{
"path": "spec/support/database/strategies/postgresql.rb",
"chars": 3474,
"preview": "# frozen_string_literal: true\n\nrequire_relative \"sql\"\n\nmodule Database\n module Strategies\n class Postgresql < Sql\n "
},
{
"path": "spec/support/database/strategies/sql.rb",
"chars": 1973,
"preview": "# frozen_string_literal: true\n\nrequire_relative \"abstract\"\nrequire \"hanami/utils/blank\"\nrequire \"pathname\"\nrequire \"stri"
},
{
"path": "spec/support/database/strategies/sqlite.rb",
"chars": 1347,
"preview": "# frozen_string_literal: true\n\nrequire_relative \"sql\"\nrequire \"pathname\"\n\nmodule Database\n module Strategies\n class "
},
{
"path": "spec/support/database.rb",
"chars": 934,
"preview": "# frozen_string_literal: true\n\nmodule Database\n class Setup\n DEFAULT_ADAPTER = \"sqlite\"\n\n def initialize(adapter:"
},
{
"path": "spec/support/fixtures/database_migrations/20150612081248_column_types.rb",
"chars": 4280,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n change do\n case Database.engine\n when :sqlite\n cr"
},
{
"path": "spec/support/fixtures/database_migrations/20150612084656_default_values.rb",
"chars": 2159,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n change do\n case Database.engine\n when :sqlite\n cr"
},
{
"path": "spec/support/fixtures/database_migrations/20150612093458_null_constraints.rb",
"chars": 226,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n change do\n create_table :null_constraints do\n column"
},
{
"path": "spec/support/fixtures/database_migrations/20150612093810_column_indexes.rb",
"chars": 412,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n change do\n create_table :column_indexes do\n column :"
},
{
"path": "spec/support/fixtures/database_migrations/20150612094740_primary_keys.rb",
"chars": 413,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n change do\n create_table :primary_keys_1 do\n primary_"
},
{
"path": "spec/support/fixtures/database_migrations/20150612115204_foreign_keys.rb",
"chars": 287,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n change do\n create_table :artists do\n primary_key :id"
},
{
"path": "spec/support/fixtures/database_migrations/20150612122233_table_constraints.rb",
"chars": 839,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n change do\n case ENV[\"HANAMI_DATABASE_TYPE\"]\n when \"sql"
},
{
"path": "spec/support/fixtures/database_migrations/20150612124205_table_alterations.rb",
"chars": 1923,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n change do\n case ENV[\"HANAMI_DATABASE_TYPE\"]\n when \"sql"
},
{
"path": "spec/support/fixtures/database_migrations/20160830094800_create_users.rb",
"chars": 681,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n change do\n drop_table? :users\n create_table? :users "
},
{
"path": "spec/support/fixtures/database_migrations/20160830094851_create_authors.rb",
"chars": 296,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n change do\n drop_table? :authors\n create_table? :auth"
},
{
"path": "spec/support/fixtures/database_migrations/20160830094941_create_books.rb",
"chars": 419,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n change do\n drop_table? :books\n create_table? :books "
},
{
"path": "spec/support/fixtures/database_migrations/20160830095033_create_t_operator.rb",
"chars": 211,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n change do\n drop_table? :t_operator\n create_table? :t"
},
{
"path": "spec/support/fixtures/database_migrations/20160905125728_create_source_files.rb",
"chars": 693,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n change do\n case Database.engine\n when :postgresql\n "
},
{
"path": "spec/support/fixtures/database_migrations/20160909150704_create_avatars.rb",
"chars": 325,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n change do\n drop_table? :avatars\n create_table? :avat"
},
{
"path": "spec/support/fixtures/database_migrations/20161104143844_create_warehouses.rb",
"chars": 337,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n change do\n drop_table? :warehouses\n create_table? :w"
},
{
"path": "spec/support/fixtures/database_migrations/20161114094644_create_products.rb",
"chars": 347,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n change do\n case Database.engine\n when :postgresql\n "
},
{
"path": "spec/support/fixtures/database_migrations/20170103142428_create_colors.rb",
"chars": 518,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n change do\n case Database.engine\n when :postgresql\n "
},
{
"path": "spec/support/fixtures/database_migrations/20170124081339_create_labels.rb",
"chars": 142,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n change do\n create_table :labels do\n column :id, Inte"
},
{
"path": "spec/support/fixtures/database_migrations/20170517115243_create_tokens.rb",
"chars": 193,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n change do\n drop_table? :tokens\n create_table? :token"
},
{
"path": "spec/support/fixtures/database_migrations/20170519172332_create_categories.rb",
"chars": 449,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n change do\n drop_table? :categories\n create_table? :c"
},
{
"path": "spec/support/fixtures/database_migrations/20171002201227_create_posts_and_comments.rb",
"chars": 486,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n change do\n drop_table? :posts\n create_table? :posts "
},
{
"path": "spec/support/fixtures/empty_migrations/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "spec/support/fixtures/migrations/20160831073534_create_reviews.rb",
"chars": 217,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n up do\n create_table :reviews do\n primary_key :id\n "
},
{
"path": "spec/support/fixtures/migrations/20160831090612_add_rating_to_reviews.rb",
"chars": 183,
"preview": "# frozen_string_literal: true\n\nHanami::Model.migration do\n up do\n add_column :reviews, :rating, \"integer\", default: "
},
{
"path": "spec/support/fixtures.rb",
"chars": 7264,
"preview": "# frozen_string_literal: true\n\nrequire \"ostruct\"\n\nclass BaseParams < OpenStruct\n def to_hash\n to_h\n end\nend\n\nclass "
},
{
"path": "spec/support/platform/ci.rb",
"chars": 586,
"preview": "# frozen_string_literal: true\n\nmodule Platform\n module Ci\n def self.ci?(name)\n current == name\n end\n\n def"
},
{
"path": "spec/support/platform/db.rb",
"chars": 174,
"preview": "# frozen_string_literal: true\n\nmodule Platform\n module Db\n def self.db?(name)\n current == name\n end\n\n def"
},
{
"path": "spec/support/platform/engine.rb",
"chars": 410,
"preview": "# frozen_string_literal: true\n\nrequire \"hanami/utils\"\n\nmodule Platform\n module Engine\n def self.engine?(name)\n "
},
{
"path": "spec/support/platform/matcher.rb",
"chars": 1731,
"preview": "# frozen_string_literal: true\n\nrequire \"hanami/utils/basic_object\"\n\nmodule Platform\n class Matcher\n class Nope < Han"
},
{
"path": "spec/support/platform/os.rb",
"chars": 285,
"preview": "# frozen_string_literal: true\n\nrequire \"rbconfig\"\n\nmodule Platform\n module Os\n def self.os?(name)\n current == n"
},
{
"path": "spec/support/platform.rb",
"chars": 559,
"preview": "# frozen_string_literal: true\n\nmodule Platform\n require_relative \"platform/os\"\n require_relative \"platform/ci\"\n requi"
},
{
"path": "spec/support/rspec.rb",
"chars": 594,
"preview": "# frozen_string_literal: true\n\nRSpec.configure do |config|\n config.expect_with :rspec do |expectations|\n expectation"
},
{
"path": "spec/support/test_io.rb",
"chars": 253,
"preview": "# frozen_string_literal: true\n\nmodule TestIO\n def self.with_stdout\n stdout = $stdout\n $stdout = stream\n yield\n"
},
{
"path": "spec/unit/hanami/entity/automatic_schema_spec.rb",
"chars": 4278,
"preview": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Entity do\n describe \"automatic schema\" do\n let(:described_clas"
},
{
"path": "spec/unit/hanami/entity/manual_schema/base_spec.rb",
"chars": 6516,
"preview": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Entity do\n describe \"manual schema (base)\" do\n let(:described_"
},
{
"path": "spec/unit/hanami/entity/manual_schema/strict_spec.rb",
"chars": 1807,
"preview": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Entity do\n describe \"manual schema (strict)\" do\n let(:describe"
},
{
"path": "spec/unit/hanami/entity/manual_schema/types_spec.rb",
"chars": 600,
"preview": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Entity do\n describe \"manual schema (types)\" do\n [nil, :schema,"
},
{
"path": "spec/unit/hanami/entity/schema/definition_spec.rb",
"chars": 1491,
"preview": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Entity::Schema::Definition do\n let(:described_class) { Hanami::En"
},
{
"path": "spec/unit/hanami/entity/schema/schemaless_spec.rb",
"chars": 733,
"preview": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Entity::Schema::Schemaless do\n let(:subject) { Hanami::Entity::Sc"
},
{
"path": "spec/unit/hanami/entity/schema_spec.rb",
"chars": 1224,
"preview": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Entity::Schema do\n let(:described_class) { Hanami::Entity::Schema"
},
{
"path": "spec/unit/hanami/entity/schemaless_spec.rb",
"chars": 2861,
"preview": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Entity do\n describe \"schemaless\" do\n let(:described_class) do\n"
},
{
"path": "spec/unit/hanami/entity_spec.rb",
"chars": 2558,
"preview": "# frozen_string_literal: true\n\nrequire \"ostruct\"\n\nRSpec.describe Hanami::Entity do\n let(:described_class) do\n Class."
},
{
"path": "spec/unit/hanami/model/check_constraint_validation_error_spec.rb",
"chars": 544,
"preview": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::CheckConstraintViolationError do\n it \"inherits from Hanami"
},
{
"path": "spec/unit/hanami/model/configuration_spec.rb",
"chars": 2449,
"preview": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::Configuration do\n before do\n database_directory = Pathn"
},
{
"path": "spec/unit/hanami/model/constraint_violation_error_spec.rb",
"chars": 495,
"preview": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::ConstraintViolationError do\n it \"inherits from Hanami::Mod"
},
{
"path": "spec/unit/hanami/model/disconnect_spec.rb",
"chars": 1251,
"preview": "# frozen_string_literal: true\n\n# This test is tightly coupled to Sequel\n#\n# We should improve connection management via "
},
{
"path": "spec/unit/hanami/model/error_spec.rb",
"chars": 182,
"preview": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::Error do\n it \"inherits from StandardError\" do\n expect(d"
},
{
"path": "spec/unit/hanami/model/foreign_key_constraint_violation_error_spec.rb",
"chars": 555,
"preview": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::ForeignKeyConstraintViolationError do\n it \"inherits from H"
},
{
"path": "spec/unit/hanami/model/load_spec.rb",
"chars": 439,
"preview": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model.load!\" do\n let(:message) { \"Cannot find corresponding type"
},
{
"path": "spec/unit/hanami/model/mapped_relation_spec.rb",
"chars": 499,
"preview": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::MappedRelation do\n subject { described_class.new(relation)"
},
{
"path": "spec/unit/hanami/model/migrator/adapter_spec.rb",
"chars": 2457,
"preview": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::Migrator::Adapter do\n extend PlatformHelpers\n\n subject { "
},
{
"path": "spec/unit/hanami/model/migrator/connection_spec.rb",
"chars": 6964,
"preview": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::Migrator::Connection do\n extend PlatformHelpers\n\n let(:co"
},
{
"path": "spec/unit/hanami/model/migrator/mysql.rb",
"chars": 14859,
"preview": "# frozen_string_literal: true\n\nrequire \"ostruct\"\nrequire \"securerandom\"\n\nRSpec.shared_examples \"migrator_mysql\" do\n let"
},
{
"path": "spec/unit/hanami/model/migrator/postgresql.rb",
"chars": 17121,
"preview": "# frozen_string_literal: true\n\nrequire \"ostruct\"\nrequire \"securerandom\"\n\nRSpec.shared_examples \"migrator_postgresql\" do\n"
},
{
"path": "spec/unit/hanami/model/migrator/sqlite.rb",
"chars": 13458,
"preview": "# frozen_string_literal: true\n\nrequire \"ostruct\"\nrequire \"securerandom\"\n\nRSpec.shared_examples \"migrator_sqlite\" do\n le"
},
{
"path": "spec/unit/hanami/model/migrator_spec.rb",
"chars": 208,
"preview": "# frozen_string_literal: true\n\nrequire \"hanami/model/migrator\"\nrequire_relative \"./migrator/#{Database.engine}\"\n\nRSpec.d"
},
{
"path": "spec/unit/hanami/model/not_null_constraint_violation_error_spec.rb",
"chars": 549,
"preview": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::NotNullConstraintViolationError do\n it \"inherits from Hana"
},
{
"path": "spec/unit/hanami/model/sql/console/mysql.rb",
"chars": 494,
"preview": "# frozen_string_literal: true\n\nrequire \"hanami/model/sql/consoles/mysql\"\n\nRSpec.shared_examples \"sql_console_mysql\" do\n "
},
{
"path": "spec/unit/hanami/model/sql/console/postgresql.rb",
"chars": 1785,
"preview": "# frozen_string_literal: true\n\nrequire \"hanami/model/sql/consoles/postgresql\"\n\nRSpec.shared_examples \"sql_console_postgr"
},
{
"path": "spec/unit/hanami/model/sql/console/sqlite.rb",
"chars": 747,
"preview": "# frozen_string_literal: true\n\nrequire \"hanami/model/sql/consoles/sqlite\"\n\nRSpec.shared_examples \"sql_console_sqlite\" do"
},
{
"path": "spec/unit/hanami/model/sql/console_spec.rb",
"chars": 1727,
"preview": "# frozen_string_literal: true\n\nrequire \"hanami/model/sql/console\"\n\nRSpec.describe Hanami::Model::Sql::Console do\n descr"
},
{
"path": "spec/unit/hanami/model/sql/entity/schema/automatic_spec.rb",
"chars": 1068,
"preview": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::Sql::Entity::Schema do\n describe \"automatic\" do\n subjec"
},
{
"path": "spec/unit/hanami/model/sql/entity/schema/mapping_spec.rb",
"chars": 975,
"preview": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::Sql::Entity::Schema do\n describe \"mapping\" do\n subject "
},
{
"path": "spec/unit/hanami/model/sql/schema/array_spec.rb",
"chars": 2204,
"preview": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model::Sql::Types::Schema::Array\" do\n let(:described_class) { Ha"
},
{
"path": "spec/unit/hanami/model/sql/schema/bool_spec.rb",
"chars": 2652,
"preview": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model::Sql::Types::Schema::Bool\" do\n let(:described_class) { Han"
},
{
"path": "spec/unit/hanami/model/sql/schema/date_spec.rb",
"chars": 2371,
"preview": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model::Sql::Types::Schema::Date\" do\n let(:described_class) { Han"
},
{
"path": "spec/unit/hanami/model/sql/schema/date_time_spec.rb",
"chars": 2476,
"preview": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model::Sql::Types::Schema::DateTime\" do\n let(:described_class) {"
},
{
"path": "spec/unit/hanami/model/sql/schema/decimal_spec.rb",
"chars": 2476,
"preview": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model::Sql::Types::Schema::Decimal\" do\n let(:described_class) { "
},
{
"path": "spec/unit/hanami/model/sql/schema/float_spec.rb",
"chars": 2625,
"preview": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model::Sql::Types::Schema::Float\" do\n let(:described_class) { Ha"
},
{
"path": "spec/unit/hanami/model/sql/schema/hash_spec.rb",
"chars": 2200,
"preview": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model::Sql::Types::Schema::Hash\" do\n let(:described_class) { Han"
},
{
"path": "spec/unit/hanami/model/sql/schema/int_spec.rb",
"chars": 2354,
"preview": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model::Sql::Types::Schema::Int\" do\n let(:described_class) { Hana"
},
{
"path": "spec/unit/hanami/model/sql/schema/string_spec.rb",
"chars": 1316,
"preview": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model::Sql::Types::Schema::String\" do\n let(:described_class) { H"
},
{
"path": "spec/unit/hanami/model/sql/schema/time_spec.rb",
"chars": 2427,
"preview": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model::Sql::Types::Schema::Time\" do\n let(:described_class) { Han"
},
{
"path": "spec/unit/hanami/model/sql_spec.rb",
"chars": 1308,
"preview": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::Sql do\n describe \".migration\" do\n it \"returns a new mig"
},
{
"path": "spec/unit/hanami/model/unique_constraint_violation_error_spec.rb",
"chars": 546,
"preview": "# frozen_string_literal: true\n\nRSpec.describe Hanami::Model::UniqueConstraintViolationError do\n it \"inherits from Hanam"
},
{
"path": "spec/unit/hanami/model/version_spec.rb",
"chars": 160,
"preview": "# frozen_string_literal: true\n\nRSpec.describe \"Hanami::Model::VERSION\" do\n it \"exposes version\" do\n expect(Hanami::M"
}
]
About this extraction
This page contains the full source code of the hanami/model GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 150 files (414.9 KB), approximately 112.1k tokens, and a symbol index with 773 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.