hanami-model 0.7.0 → 1.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/README.md +24 -5
- data/hanami-model.gemspec +3 -3
- data/lib/hanami/entity.rb +1 -1
- data/lib/hanami/entity/schema.rb +1 -1
- data/lib/hanami/model.rb +4 -10
- data/lib/hanami/model/associations/has_many.rb +1 -1
- data/lib/hanami/model/configuration.rb +65 -10
- data/lib/hanami/model/configurator.rb +30 -0
- data/lib/hanami/model/error.rb +13 -1
- data/lib/hanami/model/mapped_relation.rb +33 -0
- data/lib/hanami/model/mapping.rb +10 -2
- data/lib/hanami/model/migrator/adapter.rb +7 -20
- data/lib/hanami/model/migrator/connection.rb +39 -23
- data/lib/hanami/model/migrator/logger.rb +33 -0
- data/lib/hanami/model/migrator/mysql_adapter.rb +8 -5
- data/lib/hanami/model/migrator/postgres_adapter.rb +8 -5
- data/lib/hanami/model/migrator/sqlite_adapter.rb +2 -2
- data/lib/hanami/model/plugins/schema.rb +1 -1
- data/lib/hanami/model/plugins/timestamps.rb +2 -2
- data/lib/hanami/model/sql.rb +2 -0
- data/lib/hanami/model/sql/types.rb +4 -3
- data/lib/hanami/model/version.rb +1 -1
- data/lib/hanami/repository.rb +48 -25
- metadata +13 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 396fbb6a95d9e5526ff1e2415246d0bebc3f8413
|
4
|
+
data.tar.gz: 67a3c00a1e9c29ced838ba349082db34262edd1d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b47a74240ea4e92273809b248531ce3d06b301b4e6ab5dc9e588f8bd1297157d368b1ee4725281a401f578db1fbf17a698be2a03844718c8f7c73c594e19288b
|
7
|
+
data.tar.gz: 9307402f7125c83e3d9e3e1b1365ffb7cdcb661167bc1348969e52d13658edbf0ba28110e4923b1da4a1461554ee308564a9b6853b2240633ffb4c517975677f
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,24 @@
|
|
1
1
|
# Hanami::Model
|
2
2
|
A persistence layer for Hanami
|
3
3
|
|
4
|
+
## v1.0.0.beta1 - 2017-02-14
|
5
|
+
### Added
|
6
|
+
- [Luca Guidi] Official support for Ruby: MRI 2.4
|
7
|
+
- [Luca Guidi] Introduced `Repository#read` to fetch from database with raw SQL string
|
8
|
+
- [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.
|
9
|
+
- [Luca Guidi & Alfonso Uceda] Added `Hanami::Model::Configuration#gateway` to configure gateway and the raw connection
|
10
|
+
- [Luca Guidi] Added `Hanami::Model::Configuration#logger` to configure a logger
|
11
|
+
- [Luca Guidi] Database operations (including migrations) print informations to standard output
|
12
|
+
|
13
|
+
### Fixed
|
14
|
+
- [Thorbjørn Hermansen] Ensure repository to not override given timestamps
|
15
|
+
- [Luca Guidi] Raise `Hanami::Model::MissingPrimaryKeyError` if `Repository#find` is ran against a database w/o a primary key
|
16
|
+
- [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`)
|
17
|
+
|
18
|
+
### Changed
|
19
|
+
- [Luca Guidi] Automap the main relation in a repository, by removing the need of use `.as(:entity)`
|
20
|
+
- [Luca Guidi] Raise an `Hanami::Model::UnknownDatabaseTypeError` when the application is loaded and there is an unknown column type in the database
|
21
|
+
|
4
22
|
## v0.7.0 - 2016-11-15
|
5
23
|
### Added
|
6
24
|
- [Luca Guidi] `Hanami::Entity` defines an automatic schema for SQL databases
|
data/README.md
CHANGED
@@ -127,7 +127,7 @@ This architecture has several advantages:
|
|
127
127
|
|
128
128
|
* Multiple data sources can easily coexist in an application
|
129
129
|
|
130
|
-
When a class
|
130
|
+
When a class inherits from `Hanami::Repository`, it will receive the following interface:
|
131
131
|
|
132
132
|
* `#create(data)` – Create a record for the given data (or entity)
|
133
133
|
* `#update(id, data)` – Update the record corresponding to the given id by setting the given data (or entity)
|
@@ -207,7 +207,7 @@ class UserRepository < Hanami::Repository
|
|
207
207
|
end
|
208
208
|
end
|
209
209
|
```
|
210
|
-
**NOTE:** This feature should be used only when **_automapping_** fails because the naming mismatch.
|
210
|
+
**NOTE:** This feature should be used only when **_automapping_** fails because of the naming mismatch.
|
211
211
|
|
212
212
|
### Conventions
|
213
213
|
|
@@ -216,7 +216,7 @@ end
|
|
216
216
|
### Thread safety
|
217
217
|
|
218
218
|
**Hanami::Model**'s is thread safe during the runtime, but it isn't during the loading process.
|
219
|
-
The mapper compiles some code internally, be sure to safely load it before your application starts.
|
219
|
+
The mapper compiles some code internally, so be sure to safely load it before your application starts.
|
220
220
|
|
221
221
|
```ruby
|
222
222
|
Mutex.new.synchronize do
|
@@ -224,7 +224,7 @@ Mutex.new.synchronize do
|
|
224
224
|
end
|
225
225
|
```
|
226
226
|
|
227
|
-
**This is not necessary
|
227
|
+
**This is not necessary when Hanami::Model is used within a Hanami application.**
|
228
228
|
|
229
229
|
## Features
|
230
230
|
|
@@ -258,6 +258,25 @@ puts user.created_at.to_s # => "2016-09-19 13:40:13 UTC"
|
|
258
258
|
puts user.updated_at.to_s # => "2016-09-19 13:40:16 UTC"
|
259
259
|
```
|
260
260
|
|
261
|
+
## Configuration
|
262
|
+
|
263
|
+
### Logging
|
264
|
+
|
265
|
+
In order to log database operations, you can configure a logger:
|
266
|
+
|
267
|
+
```ruby
|
268
|
+
Hanami::Model.configure do
|
269
|
+
# ...
|
270
|
+
logger "log/development.log", level: :debug
|
271
|
+
end
|
272
|
+
```
|
273
|
+
|
274
|
+
It accepts the following arguments:
|
275
|
+
|
276
|
+
* `stream`: a Ruby StringIO object - it can be `$stdout` or a path to file (eg. `"log/development.log"`) - Defaults to `$stdout`
|
277
|
+
* `:level`: logging level - it can be: `:debug`, `:info`, `:warn`, `:error`, `:fatal`, `:unknown` - Defaults to `:debug`
|
278
|
+
* `:formatter`: logging formatter - it can be: `:default` or `:json` - Defaults to `:default`
|
279
|
+
|
261
280
|
## Versioning
|
262
281
|
|
263
282
|
__Hanami::Model__ uses [Semantic Versioning 2.0.0](http://semver.org)
|
@@ -272,6 +291,6 @@ __Hanami::Model__ uses [Semantic Versioning 2.0.0](http://semver.org)
|
|
272
291
|
|
273
292
|
## Copyright
|
274
293
|
|
275
|
-
Copyright © 2014-
|
294
|
+
Copyright © 2014-2017 Luca Guidi – Released under MIT License
|
276
295
|
|
277
296
|
This project was formerly known as Lotus (`lotus-model`).
|
data/hanami-model.gemspec
CHANGED
@@ -19,9 +19,9 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
spec.required_ruby_version = '>= 2.3.0'
|
21
21
|
|
22
|
-
spec.add_runtime_dependency 'hanami-utils', '~> 0.
|
23
|
-
spec.add_runtime_dependency 'rom-sql', '~> 0
|
24
|
-
spec.add_runtime_dependency 'rom-repository', '~> 0
|
22
|
+
spec.add_runtime_dependency 'hanami-utils', '~> 1.0.0.beta'
|
23
|
+
spec.add_runtime_dependency 'rom-sql', '~> 1.0'
|
24
|
+
spec.add_runtime_dependency 'rom-repository', '~> 1.0'
|
25
25
|
spec.add_runtime_dependency 'dry-types', '~> 0.9'
|
26
26
|
spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
|
27
27
|
|
data/lib/hanami/entity.rb
CHANGED
data/lib/hanami/entity/schema.rb
CHANGED
data/lib/hanami/model.rb
CHANGED
@@ -17,7 +17,7 @@ module Hanami
|
|
17
17
|
|
18
18
|
# @api private
|
19
19
|
# @since 0.7.0
|
20
|
-
@__repositories__ = Concurrent::Array.new
|
20
|
+
@__repositories__ = Concurrent::Array.new
|
21
21
|
|
22
22
|
class << self
|
23
23
|
# @since 0.7.0
|
@@ -72,15 +72,9 @@ module Hanami
|
|
72
72
|
end
|
73
73
|
|
74
74
|
# @since 0.1.0
|
75
|
-
def self.load!(&blk)
|
76
|
-
configuration.
|
77
|
-
|
78
|
-
repositories.each(&:load!)
|
79
|
-
|
80
|
-
@container = ROM.container(configuration)
|
81
|
-
configuration.define_entities_mappings(@container, repositories)
|
82
|
-
|
83
|
-
@loaded = true
|
75
|
+
def self.load!(&blk)
|
76
|
+
@container = configuration.load!(repositories, &blk)
|
77
|
+
@loaded = true
|
84
78
|
end
|
85
79
|
end
|
86
80
|
end
|
@@ -186,7 +186,7 @@ module Hanami
|
|
186
186
|
def _build_scope
|
187
187
|
result = relation(target)
|
188
188
|
result = result.where(foreign_key => subject.fetch(primary_key)) unless subject.nil?
|
189
|
-
result.as(
|
189
|
+
result.as(Model::MappedRelation.mapper_name)
|
190
190
|
end
|
191
191
|
|
192
192
|
# @since 0.7.0
|
@@ -8,7 +8,7 @@ module Hanami
|
|
8
8
|
# via `Hanami::Model.configure`.
|
9
9
|
#
|
10
10
|
# @since 0.2.0
|
11
|
-
class Configuration
|
11
|
+
class Configuration
|
12
12
|
# @since 0.7.0
|
13
13
|
# @api private
|
14
14
|
attr_reader :mappings
|
@@ -17,23 +17,33 @@ module Hanami
|
|
17
17
|
# @api private
|
18
18
|
attr_reader :entities
|
19
19
|
|
20
|
+
# @since 1.0.0.beta1
|
21
|
+
# @api private
|
22
|
+
attr_reader :logger
|
23
|
+
|
24
|
+
# @since 1.0.0.beta1
|
25
|
+
# @api private
|
26
|
+
attr_reader :migrations_logger
|
27
|
+
|
20
28
|
# @since 0.2.0
|
21
29
|
# @api private
|
22
30
|
def initialize(configurator)
|
23
|
-
|
24
|
-
@
|
25
|
-
@
|
26
|
-
@
|
27
|
-
@
|
31
|
+
@backend = configurator.backend
|
32
|
+
@url = configurator.url
|
33
|
+
@migrations = configurator._migrations
|
34
|
+
@schema = configurator._schema
|
35
|
+
@gateway_config = configurator._gateway
|
36
|
+
@logger = configurator._logger
|
37
|
+
@migrations_logger = configurator.migrations_logger
|
38
|
+
@mappings = {}
|
39
|
+
@entities = {}
|
28
40
|
end
|
29
41
|
|
30
42
|
# NOTE: This must be changed when we want to support several adapters at the time
|
31
43
|
#
|
32
44
|
# @since 0.7.0
|
33
45
|
# @api private
|
34
|
-
|
35
|
-
connection.url
|
36
|
-
end
|
46
|
+
attr_reader :url
|
37
47
|
|
38
48
|
# NOTE: This must be changed when we want to support several adapters at the time
|
39
49
|
#
|
@@ -48,7 +58,7 @@ module Hanami
|
|
48
58
|
# @since 0.7.0
|
49
59
|
# @api private
|
50
60
|
def gateway
|
51
|
-
|
61
|
+
gateways[:default]
|
52
62
|
end
|
53
63
|
|
54
64
|
# Root directory
|
@@ -98,6 +108,51 @@ module Hanami
|
|
98
108
|
entity.schema = Sql::Entity::Schema.new(entities, container.relations[relation], mappings.fetch(relation))
|
99
109
|
end
|
100
110
|
end
|
111
|
+
|
112
|
+
# @since 1.0.0.beta1
|
113
|
+
# @api private
|
114
|
+
def configure_gateway
|
115
|
+
@gateway_config.call(gateway) unless @gateway_config.nil?
|
116
|
+
end
|
117
|
+
|
118
|
+
# @since 1.0.0.beta1
|
119
|
+
# @api private
|
120
|
+
def logger=(value)
|
121
|
+
return if value.nil?
|
122
|
+
gateway.use_logger(@logger = value)
|
123
|
+
end
|
124
|
+
|
125
|
+
# @since 1.0.0.beta1
|
126
|
+
# @api private
|
127
|
+
def rom
|
128
|
+
@rom ||= ROM::Configuration.new(@backend, @url, infer_relations: false)
|
129
|
+
end
|
130
|
+
|
131
|
+
# @since 1.0.0.beta1
|
132
|
+
# @api private
|
133
|
+
def load!(repositories, &blk) # rubocop:disable Metrics/AbcSize
|
134
|
+
rom.setup.auto_registration(config.directory.to_s) unless config.directory.nil?
|
135
|
+
rom.instance_eval(&blk) if block_given?
|
136
|
+
configure_gateway
|
137
|
+
repositories.each(&:load!)
|
138
|
+
self.logger = logger
|
139
|
+
|
140
|
+
container = ROM.container(rom)
|
141
|
+
define_entities_mappings(container, repositories)
|
142
|
+
container
|
143
|
+
rescue => e
|
144
|
+
raise Hanami::Model::Error.for(e)
|
145
|
+
end
|
146
|
+
|
147
|
+
# @since 1.0.0.beta1
|
148
|
+
# @api private
|
149
|
+
def method_missing(method_name, *args, &blk)
|
150
|
+
if rom.respond_to?(method_name)
|
151
|
+
rom.__send__(method_name, *args, &blk)
|
152
|
+
else
|
153
|
+
super
|
154
|
+
end
|
155
|
+
end
|
101
156
|
end
|
102
157
|
end
|
103
158
|
end
|
@@ -25,12 +25,27 @@ module Hanami
|
|
25
25
|
# @api private
|
26
26
|
attr_reader :_schema
|
27
27
|
|
28
|
+
# @since 1.0.0.beta1
|
29
|
+
# @api private
|
30
|
+
attr_reader :_logger
|
31
|
+
|
32
|
+
# @since 1.0.0.beta1
|
33
|
+
# @api private
|
34
|
+
attr_reader :_gateway
|
35
|
+
|
28
36
|
# @since 0.7.0
|
29
37
|
# @api private
|
30
38
|
def self.build(&block)
|
31
39
|
new.tap { |config| config.instance_eval(&block) }
|
32
40
|
end
|
33
41
|
|
42
|
+
# @since 1.0.0.beta1
|
43
|
+
# @api private
|
44
|
+
def migrations_logger(stream = $stdout)
|
45
|
+
require 'hanami/model/migrator/logger'
|
46
|
+
@migrations_logger ||= Hanami::Model::Migrator::Logger.new(stream)
|
47
|
+
end
|
48
|
+
|
34
49
|
private
|
35
50
|
|
36
51
|
# @since 0.7.0
|
@@ -57,6 +72,21 @@ module Hanami
|
|
57
72
|
def schema(path)
|
58
73
|
@_schema = path
|
59
74
|
end
|
75
|
+
|
76
|
+
# @since 1.0.0.beta1
|
77
|
+
# @api private
|
78
|
+
def logger(stream, options = {})
|
79
|
+
require 'hanami/logger'
|
80
|
+
|
81
|
+
opts = options.merge(stream: stream)
|
82
|
+
@_logger = Hanami::Logger.new('hanami.model', opts)
|
83
|
+
end
|
84
|
+
|
85
|
+
# @since 1.0.0.beta1
|
86
|
+
# @api private
|
87
|
+
def gateway(&blk)
|
88
|
+
@_gateway = blk
|
89
|
+
end
|
60
90
|
end
|
61
91
|
end
|
62
92
|
end
|
data/lib/hanami/model/error.rb
CHANGED
@@ -8,7 +8,7 @@ module Hanami
|
|
8
8
|
class Error < ::StandardError
|
9
9
|
# @api private
|
10
10
|
# @since 0.7.0
|
11
|
-
@__mapping__ = Concurrent::Map.new
|
11
|
+
@__mapping__ = Concurrent::Map.new
|
12
12
|
|
13
13
|
# @api private
|
14
14
|
# @since 0.7.0
|
@@ -88,5 +88,17 @@ module Hanami
|
|
88
88
|
super
|
89
89
|
end
|
90
90
|
end
|
91
|
+
|
92
|
+
# Unknown database type error for repository auto-mapping
|
93
|
+
#
|
94
|
+
# @since 1.0.0.beta1
|
95
|
+
class UnknownDatabaseTypeError < Error
|
96
|
+
end
|
97
|
+
|
98
|
+
# Unknown primary key error
|
99
|
+
#
|
100
|
+
# @since 1.0.0.beta1
|
101
|
+
class MissingPrimaryKeyError < Error
|
102
|
+
end
|
91
103
|
end
|
92
104
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Hanami
|
2
|
+
module Model
|
3
|
+
# Mapped proxy for ROM relations.
|
4
|
+
#
|
5
|
+
# It eliminates the need of use #as for repository queries
|
6
|
+
#
|
7
|
+
# @since 1.0.0.beta1
|
8
|
+
# @api private
|
9
|
+
class MappedRelation < SimpleDelegator
|
10
|
+
# Mapper name.
|
11
|
+
#
|
12
|
+
# With ROM mapping there is a link between the entity class and a generic
|
13
|
+
# reference for it. Example: <tt>BookRepository</tt> references <tt>Book</tt>
|
14
|
+
# as <tt>:entity</tt>.
|
15
|
+
#
|
16
|
+
# @since 1.0.0.beta1
|
17
|
+
# @api private
|
18
|
+
MAPPER_NAME = :entity
|
19
|
+
|
20
|
+
# @since 1.0.0.beta1
|
21
|
+
# @api private
|
22
|
+
def self.mapper_name
|
23
|
+
MAPPER_NAME
|
24
|
+
end
|
25
|
+
|
26
|
+
# @since 1.0.0.beta1
|
27
|
+
# @api private
|
28
|
+
def initialize(relation)
|
29
|
+
super(relation.as(self.class.mapper_name))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/hanami/model/mapping.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'transproc'
|
1
|
+
require 'transproc/all'
|
2
2
|
|
3
3
|
module Hanami
|
4
4
|
module Model
|
@@ -6,11 +6,19 @@ module Hanami
|
|
6
6
|
#
|
7
7
|
# @since 0.1.0
|
8
8
|
class Mapping
|
9
|
+
extend Transproc::Registry
|
10
|
+
|
11
|
+
import Transproc::HashTransformations
|
12
|
+
|
9
13
|
def initialize(&blk)
|
10
14
|
@attributes = {}
|
11
15
|
@r_attributes = {}
|
12
16
|
instance_eval(&blk)
|
13
|
-
@processor = @attributes.empty? ? ::Hash :
|
17
|
+
@processor = @attributes.empty? ? ::Hash : t(:rename_keys, @attributes)
|
18
|
+
end
|
19
|
+
|
20
|
+
def t(name, *args)
|
21
|
+
self.class[name, *args]
|
14
22
|
end
|
15
23
|
|
16
24
|
def model(entity)
|
@@ -26,7 +26,7 @@ module Hanami
|
|
26
26
|
# @since 0.4.0
|
27
27
|
# @api private
|
28
28
|
def self.for(configuration) # rubocop:disable Metrics/MethodLength
|
29
|
-
connection =
|
29
|
+
connection = Connection.new(configuration)
|
30
30
|
|
31
31
|
case connection.database_type
|
32
32
|
when :sqlite
|
@@ -40,30 +40,15 @@ module Hanami
|
|
40
40
|
MySQLAdapter
|
41
41
|
else
|
42
42
|
self
|
43
|
-
end.new(connection
|
44
|
-
end
|
45
|
-
|
46
|
-
class << self
|
47
|
-
private
|
48
|
-
|
49
|
-
# @since 0.7.0
|
50
|
-
# @api private
|
51
|
-
def connection_for(configuration)
|
52
|
-
Sequel.connect(
|
53
|
-
configuration.url
|
54
|
-
)
|
55
|
-
rescue Sequel::AdapterNotFound
|
56
|
-
raise MigrationError.new("Current adapter (#{configuration.adapter.type}) doesn't support SQL database operations.")
|
57
|
-
end
|
43
|
+
end.new(connection)
|
58
44
|
end
|
59
45
|
|
60
46
|
# Initialize an adapter
|
61
47
|
#
|
62
48
|
# @since 0.4.0
|
63
49
|
# @api private
|
64
|
-
def initialize(connection
|
65
|
-
@connection =
|
66
|
-
@schema = configuration.schema
|
50
|
+
def initialize(connection)
|
51
|
+
@connection = connection
|
67
52
|
end
|
68
53
|
|
69
54
|
# Create database.
|
@@ -128,7 +113,9 @@ module Hanami
|
|
128
113
|
|
129
114
|
# @since 0.4.0
|
130
115
|
# @api private
|
131
|
-
|
116
|
+
def schema
|
117
|
+
connection.schema
|
118
|
+
end
|
132
119
|
|
133
120
|
# Returns a database connection
|
134
121
|
#
|
@@ -8,14 +8,23 @@ module Hanami
|
|
8
8
|
# @since 0.5.0
|
9
9
|
# @api private
|
10
10
|
class Connection
|
11
|
-
# @since 0.
|
11
|
+
# @since 0.5.0
|
12
12
|
# @api private
|
13
|
-
|
13
|
+
def initialize(configuration)
|
14
|
+
@configuration = configuration
|
15
|
+
end
|
14
16
|
|
15
|
-
# @since 0.
|
17
|
+
# @since 0.7.0
|
16
18
|
# @api private
|
17
|
-
def
|
18
|
-
@raw
|
19
|
+
def raw
|
20
|
+
@raw ||= begin
|
21
|
+
Sequel.connect(
|
22
|
+
configuration.url,
|
23
|
+
loggers: [configuration.migrations_logger]
|
24
|
+
)
|
25
|
+
rescue Sequel::AdapterNotFound
|
26
|
+
raise MigrationError.new("Current adapter (#{configuration.adapter.type}) doesn't support SQL database operations.")
|
27
|
+
end
|
19
28
|
end
|
20
29
|
|
21
30
|
# Returns DB connection host
|
@@ -25,7 +34,7 @@ module Hanami
|
|
25
34
|
# @since 0.5.0
|
26
35
|
# @api private
|
27
36
|
def host
|
28
|
-
@host ||=
|
37
|
+
@host ||= parsed_uri.host
|
29
38
|
end
|
30
39
|
|
31
40
|
# Returns DB connection port
|
@@ -35,7 +44,7 @@ module Hanami
|
|
35
44
|
# @since 0.5.0
|
36
45
|
# @api private
|
37
46
|
def port
|
38
|
-
@port ||=
|
47
|
+
@port ||= parsed_uri.port
|
39
48
|
end
|
40
49
|
|
41
50
|
# Returns DB name from conenction
|
@@ -45,7 +54,7 @@ module Hanami
|
|
45
54
|
# @since 0.5.0
|
46
55
|
# @api private
|
47
56
|
def database
|
48
|
-
@database ||=
|
57
|
+
@database ||= parsed_uri.path[1..-1]
|
49
58
|
end
|
50
59
|
|
51
60
|
# Returns DB type
|
@@ -57,7 +66,14 @@ module Hanami
|
|
57
66
|
# @since 0.5.0
|
58
67
|
# @api private
|
59
68
|
def database_type
|
60
|
-
|
69
|
+
case uri
|
70
|
+
when /sqlite/
|
71
|
+
:sqlite
|
72
|
+
when /postgres/
|
73
|
+
:postgres
|
74
|
+
when /mysql/
|
75
|
+
:mysql
|
76
|
+
end
|
61
77
|
end
|
62
78
|
|
63
79
|
# Returns user from DB connection
|
@@ -67,7 +83,7 @@ module Hanami
|
|
67
83
|
# @since 0.5.0
|
68
84
|
# @api private
|
69
85
|
def user
|
70
|
-
@user ||=
|
86
|
+
@user ||= parsed_opt('user')
|
71
87
|
end
|
72
88
|
|
73
89
|
# Returns user from DB connection
|
@@ -77,7 +93,7 @@ module Hanami
|
|
77
93
|
# @since 0.5.0
|
78
94
|
# @api private
|
79
95
|
def password
|
80
|
-
@password ||=
|
96
|
+
@password ||= parsed_opt('password')
|
81
97
|
end
|
82
98
|
|
83
99
|
# Returns DB connection URI directly from adapter
|
@@ -85,7 +101,7 @@ module Hanami
|
|
85
101
|
# @since 0.5.0
|
86
102
|
# @api private
|
87
103
|
def uri
|
88
|
-
|
104
|
+
@configuration.url
|
89
105
|
end
|
90
106
|
|
91
107
|
# Returns DB connection wihout specifying database name
|
@@ -93,7 +109,7 @@ module Hanami
|
|
93
109
|
# @since 0.5.0
|
94
110
|
# @api private
|
95
111
|
def global_uri
|
96
|
-
|
112
|
+
uri.sub(parsed_uri.select(:path).first, '')
|
97
113
|
end
|
98
114
|
|
99
115
|
# Returns a boolean telling if a DB connection is from JDBC or not
|
@@ -101,7 +117,7 @@ module Hanami
|
|
101
117
|
# @since 0.5.0
|
102
118
|
# @api private
|
103
119
|
def jdbc?
|
104
|
-
!
|
120
|
+
!uri.scan('jdbc:').empty?
|
105
121
|
end
|
106
122
|
|
107
123
|
# Returns database connection URI instance without JDBC namespace
|
@@ -109,7 +125,11 @@ module Hanami
|
|
109
125
|
# @since 0.5.0
|
110
126
|
# @api private
|
111
127
|
def parsed_uri
|
112
|
-
@uri ||= URI.parse(
|
128
|
+
@uri ||= URI.parse(uri.sub('jdbc:', ''))
|
129
|
+
end
|
130
|
+
|
131
|
+
def schema
|
132
|
+
configuration.schema
|
113
133
|
end
|
114
134
|
|
115
135
|
# Return the database table for the given name
|
@@ -122,6 +142,10 @@ module Hanami
|
|
122
142
|
|
123
143
|
private
|
124
144
|
|
145
|
+
# @since 1.0.0.beta1
|
146
|
+
# @api private
|
147
|
+
attr_reader :configuration
|
148
|
+
|
125
149
|
# Returns a value of a given query string param
|
126
150
|
#
|
127
151
|
# @param option [String] which option from database connection will be extracted from URI
|
@@ -131,14 +155,6 @@ module Hanami
|
|
131
155
|
def parsed_opt(option)
|
132
156
|
parsed_uri.to_s.match(/[\?|\&]#{ option }=(\w+)\&?/).to_a.last
|
133
157
|
end
|
134
|
-
|
135
|
-
# Fetch connection options from adapter
|
136
|
-
#
|
137
|
-
# @since 0.5.0
|
138
|
-
# @api private
|
139
|
-
def opts
|
140
|
-
raw.opts
|
141
|
-
end
|
142
158
|
end
|
143
159
|
end
|
144
160
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'hanami/logger'
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module Model
|
5
|
+
class Migrator
|
6
|
+
# Automatic logger for migrations
|
7
|
+
#
|
8
|
+
# @since 1.0.0.beta1
|
9
|
+
# @api private
|
10
|
+
class Logger < Hanami::Logger
|
11
|
+
# Formatter for migrations logger
|
12
|
+
#
|
13
|
+
# @since 1.0.0.beta1
|
14
|
+
# @api private
|
15
|
+
class Formatter < Hanami::Logger::Formatter
|
16
|
+
private
|
17
|
+
|
18
|
+
# @since 1.0.0.beta1
|
19
|
+
# @api private
|
20
|
+
def _format(hash)
|
21
|
+
"[hanami] [#{hash.fetch(:severity)}] #{hash.fetch(:message)}\n"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# @since 1.0.0.beta1
|
26
|
+
# @api private
|
27
|
+
def initialize(stream)
|
28
|
+
super(nil, stream: stream, formatter: Formatter.new)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -10,16 +10,19 @@ module Hanami
|
|
10
10
|
# @api private
|
11
11
|
PASSWORD = 'MYSQL_PWD'.freeze
|
12
12
|
|
13
|
+
# @since 1.0.0.beta1
|
14
|
+
# @api private
|
15
|
+
DB_CREATION_ERROR = 'Database creation failed. If the database exists, ' \
|
16
|
+
'then its console may be open. See this issue for more details: ' \
|
17
|
+
'https://github.com/hanami/model/issues/250'.freeze
|
18
|
+
|
13
19
|
# @since 0.4.0
|
14
20
|
# @api private
|
15
|
-
def create
|
21
|
+
def create
|
16
22
|
new_connection(global: true).run %(CREATE DATABASE #{database};)
|
17
23
|
rescue Sequel::DatabaseError => e
|
18
24
|
message = if e.message.match(/database exists/) # rubocop:disable Performance/RedundantMatch
|
19
|
-
|
20
|
-
then its console may be open. See this issue for more details:\
|
21
|
-
https://github.com/hanami/model/issues/250\
|
22
|
-
"
|
25
|
+
DB_CREATION_ERROR
|
23
26
|
else
|
24
27
|
e.message
|
25
28
|
end
|
@@ -22,17 +22,20 @@ module Hanami
|
|
22
22
|
# @api private
|
23
23
|
PASSWORD = 'PGPASSWORD'.freeze
|
24
24
|
|
25
|
+
# @since 1.0.0.beta1
|
26
|
+
# @api private
|
27
|
+
DB_CREATION_ERROR = 'createdb: database creation failed. If the database exists, ' \
|
28
|
+
'then its console may be open. See this issue for more details: ' \
|
29
|
+
'https://github.com/hanami/model/issues/250'.freeze
|
30
|
+
|
25
31
|
# @since 0.4.0
|
26
32
|
# @api private
|
27
|
-
def create
|
33
|
+
def create
|
28
34
|
set_environment_variables
|
29
35
|
|
30
36
|
call_db_command('createdb') do |error_message|
|
31
37
|
message = if error_message.match(/already exists/) # rubocop:disable Performance/RedundantMatch
|
32
|
-
|
33
|
-
then its console may be open. See this issue for more details:\
|
34
|
-
https://github.com/hanami/model/issues/250\
|
35
|
-
"
|
38
|
+
DB_CREATION_ERROR
|
36
39
|
else
|
37
40
|
error_message
|
38
41
|
end
|
@@ -29,7 +29,7 @@ module Hanami
|
|
29
29
|
#
|
30
30
|
# @since 0.4.0
|
31
31
|
# @api private
|
32
|
-
def initialize(
|
32
|
+
def initialize(configuration)
|
33
33
|
super
|
34
34
|
extend Memory if memory?
|
35
35
|
end
|
@@ -70,7 +70,7 @@ module Hanami
|
|
70
70
|
# @api private
|
71
71
|
def path
|
72
72
|
root.join(
|
73
|
-
@connection.uri.sub(/\A(jdbc:sqlite
|
73
|
+
@connection.uri.sub(/\A(jdbc:sqlite:\/\/|sqlite:\/\/)/, '')
|
74
74
|
)
|
75
75
|
end
|
76
76
|
|
@@ -63,7 +63,7 @@ module Hanami
|
|
63
63
|
# @since 0.7.0
|
64
64
|
# @api private
|
65
65
|
def _touch(value, now)
|
66
|
-
value[:updated_at]
|
66
|
+
value[:updated_at] ||= now
|
67
67
|
value
|
68
68
|
end
|
69
69
|
end
|
@@ -79,7 +79,7 @@ module Hanami
|
|
79
79
|
# @api private
|
80
80
|
def _touch(value, now)
|
81
81
|
super
|
82
|
-
value[:created_at]
|
82
|
+
value[:created_at] ||= now
|
83
83
|
value
|
84
84
|
end
|
85
85
|
end
|
data/lib/hanami/model/sql.rb
CHANGED
@@ -145,6 +145,8 @@ module Hanami
|
|
145
145
|
Error.register(ROM::SQL::UniqueConstraintError, UniqueConstraintViolationError)
|
146
146
|
Error.register(ROM::SQL::CheckConstraintError, CheckConstraintViolationError)
|
147
147
|
Error.register(ROM::SQL::ForeignKeyConstraintError, ForeignKeyConstraintViolationError)
|
148
|
+
Error.register(ROM::SQL::UnknownDBTypeError, UnknownDatabaseTypeError)
|
149
|
+
Error.register(ROM::SQL::MissingPrimaryKeyError, MissingPrimaryKeyError)
|
148
150
|
|
149
151
|
Error.register(Java::JavaSql::SQLException, DatabaseError) if Utils.jruby?
|
150
152
|
end
|
@@ -60,9 +60,10 @@ module Hanami
|
|
60
60
|
#
|
61
61
|
# @since 0.7.0
|
62
62
|
# @api private
|
63
|
-
def self.coercible(
|
64
|
-
return
|
65
|
-
|
63
|
+
def self.coercible(attribute)
|
64
|
+
return attribute if attribute.constrained?
|
65
|
+
# TODO: figure out a better way of inferring coercions from schema types
|
66
|
+
MAPPING.fetch(attribute.type.with(meta: {}), attribute)
|
66
67
|
end
|
67
68
|
|
68
69
|
# Coercer for SQL associations target
|
data/lib/hanami/model/version.rb
CHANGED
data/lib/hanami/repository.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'rom-repository'
|
2
2
|
require 'hanami/model/entity_name'
|
3
3
|
require 'hanami/model/relation_name'
|
4
|
+
require 'hanami/model/mapped_relation'
|
4
5
|
require 'hanami/model/associations/dsl'
|
5
6
|
require 'hanami/model/association'
|
6
7
|
require 'hanami/utils/class'
|
@@ -107,19 +108,6 @@ module Hanami
|
|
107
108
|
# @see http://martinfowler.com/eaaCatalog/repository.html
|
108
109
|
# @see http://en.wikipedia.org/wiki/Dependency_inversion_principle
|
109
110
|
class Repository < ROM::Repository::Root
|
110
|
-
# Mapper name.
|
111
|
-
#
|
112
|
-
# With ROM mapping there is a link between the entity class and a generic
|
113
|
-
# reference for it. Example: <tt>BookRepository</tt> references <tt>Book</tt>
|
114
|
-
# as <tt>:entity</tt>.
|
115
|
-
#
|
116
|
-
# @since 0.7.0
|
117
|
-
# @api private
|
118
|
-
#
|
119
|
-
# @see Hanami::Repository.inherited
|
120
|
-
# @see Hanami::Repository.define_mapping
|
121
|
-
MAPPER_NAME = :entity
|
122
|
-
|
123
111
|
# Plugins for database commands
|
124
112
|
#
|
125
113
|
# @since 0.7.0
|
@@ -151,24 +139,33 @@ module Hanami
|
|
151
139
|
#
|
152
140
|
# @since 0.7.0
|
153
141
|
# @api private
|
154
|
-
|
142
|
+
#
|
143
|
+
# rubocop:disable Metrics/MethodLength
|
144
|
+
# rubocop:disable Metrics/AbcSize
|
145
|
+
def self.define_relation
|
155
146
|
a = @associations
|
147
|
+
s = @schema
|
156
148
|
|
157
149
|
configuration.relation(relation) do
|
158
|
-
|
159
|
-
|
150
|
+
if s.nil?
|
151
|
+
schema(infer: true) do
|
152
|
+
associations(&a) unless a.nil?
|
153
|
+
end
|
154
|
+
else
|
155
|
+
schema(&s)
|
160
156
|
end
|
161
|
-
|
162
|
-
# rubocop:disable Lint/NestedMethodDefinition
|
163
|
-
def by_primary_key(id)
|
164
|
-
where(primary_key => id)
|
165
|
-
end
|
166
|
-
# rubocop:enable Lint/NestedMethodDefinition
|
167
157
|
end
|
168
158
|
|
169
159
|
relations(relation)
|
170
160
|
root(relation)
|
161
|
+
class_eval %{
|
162
|
+
def #{relation}
|
163
|
+
Hanami::Model::MappedRelation.new(@#{relation})
|
164
|
+
end
|
165
|
+
}
|
171
166
|
end
|
167
|
+
# rubocop:enable Metrics/AbcSize
|
168
|
+
# rubocop:enable Metrics/MethodLength
|
172
169
|
|
173
170
|
# Defines the ampping between a database table and an entity.
|
174
171
|
#
|
@@ -186,7 +183,7 @@ module Hanami
|
|
186
183
|
|
187
184
|
blk = lambda do |_|
|
188
185
|
model e
|
189
|
-
register_as
|
186
|
+
register_as Model::MappedRelation.mapper_name
|
190
187
|
instance_exec(&m) unless m.nil?
|
191
188
|
end
|
192
189
|
|
@@ -225,6 +222,27 @@ module Hanami
|
|
225
222
|
@associations = blk
|
226
223
|
end
|
227
224
|
|
225
|
+
# Declare database schema
|
226
|
+
#
|
227
|
+
# NOTE: This should be used **only** when Hanami can't find a corresponding Ruby type for your column.
|
228
|
+
#
|
229
|
+
# @since 1.0.0.beta1
|
230
|
+
#
|
231
|
+
# @example
|
232
|
+
# # In this example `name` is a PostgreSQL Enum type that we want to treat like a string.
|
233
|
+
#
|
234
|
+
# class ColorRepository < Hanami::Repository
|
235
|
+
# schema do
|
236
|
+
# attribute :id, Hanami::Model::Sql::Types::Int
|
237
|
+
# attribute :name, Hanami::Model::Sql::Types::String
|
238
|
+
# attribute :created_at, Hanami::Model::Sql::Types::DateTime
|
239
|
+
# attribute :updated_at, Hanami::Model::Sql::Types::DateTime
|
240
|
+
# end
|
241
|
+
# end
|
242
|
+
def self.schema(&blk)
|
243
|
+
@schema = blk
|
244
|
+
end
|
245
|
+
|
228
246
|
# Declare mapping between database columns and entity's attributes
|
229
247
|
#
|
230
248
|
# NOTE: This should be used **only** when there is a name mismatch (eg. in legacy databases).
|
@@ -268,7 +286,7 @@ module Hanami
|
|
268
286
|
class_attribute :relation
|
269
287
|
self.relation = Model::RelationName.new(name)
|
270
288
|
|
271
|
-
commands :create, update: :
|
289
|
+
commands :create, update: :by_pk, delete: :by_pk, mapper: Model::MappedRelation.mapper_name, use: COMMAND_PLUGINS
|
272
290
|
prepend Commands
|
273
291
|
end
|
274
292
|
|
@@ -364,6 +382,9 @@ module Hanami
|
|
364
382
|
#
|
365
383
|
# @return [Hanami::Entity,NilClass] the entity, if found
|
366
384
|
#
|
385
|
+
# @raise [Hanami::Model::MissingPrimaryKeyError] if the table doesn't
|
386
|
+
# define a primary key
|
387
|
+
#
|
367
388
|
# @since 0.7.0
|
368
389
|
#
|
369
390
|
# @example
|
@@ -372,7 +393,9 @@ module Hanami
|
|
372
393
|
#
|
373
394
|
# user = repository.find(user.id)
|
374
395
|
def find(id)
|
375
|
-
root.
|
396
|
+
root.by_pk(id).as(:entity).one
|
397
|
+
rescue => e
|
398
|
+
raise Hanami::Model::Error.for(e)
|
376
399
|
end
|
377
400
|
|
378
401
|
# Return all the records for the relation
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hanami-model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0.beta1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Luca Guidi
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2017-02-14 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: hanami-utils
|
@@ -18,42 +18,42 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - "~>"
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version:
|
21
|
+
version: 1.0.0.beta
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
25
|
requirements:
|
26
26
|
- - "~>"
|
27
27
|
- !ruby/object:Gem::Version
|
28
|
-
version:
|
28
|
+
version: 1.0.0.beta
|
29
29
|
- !ruby/object:Gem::Dependency
|
30
30
|
name: rom-sql
|
31
31
|
requirement: !ruby/object:Gem::Requirement
|
32
32
|
requirements:
|
33
33
|
- - "~>"
|
34
34
|
- !ruby/object:Gem::Version
|
35
|
-
version: '0
|
35
|
+
version: '1.0'
|
36
36
|
type: :runtime
|
37
37
|
prerelease: false
|
38
38
|
version_requirements: !ruby/object:Gem::Requirement
|
39
39
|
requirements:
|
40
40
|
- - "~>"
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
version: '0
|
42
|
+
version: '1.0'
|
43
43
|
- !ruby/object:Gem::Dependency
|
44
44
|
name: rom-repository
|
45
45
|
requirement: !ruby/object:Gem::Requirement
|
46
46
|
requirements:
|
47
47
|
- - "~>"
|
48
48
|
- !ruby/object:Gem::Version
|
49
|
-
version: '0
|
49
|
+
version: '1.0'
|
50
50
|
type: :runtime
|
51
51
|
prerelease: false
|
52
52
|
version_requirements: !ruby/object:Gem::Requirement
|
53
53
|
requirements:
|
54
54
|
- - "~>"
|
55
55
|
- !ruby/object:Gem::Version
|
56
|
-
version: '0
|
56
|
+
version: '1.0'
|
57
57
|
- !ruby/object:Gem::Dependency
|
58
58
|
name: dry-types
|
59
59
|
requirement: !ruby/object:Gem::Requirement
|
@@ -149,11 +149,13 @@ files:
|
|
149
149
|
- lib/hanami/model/configurator.rb
|
150
150
|
- lib/hanami/model/entity_name.rb
|
151
151
|
- lib/hanami/model/error.rb
|
152
|
+
- lib/hanami/model/mapped_relation.rb
|
152
153
|
- lib/hanami/model/mapping.rb
|
153
154
|
- lib/hanami/model/migration.rb
|
154
155
|
- lib/hanami/model/migrator.rb
|
155
156
|
- lib/hanami/model/migrator/adapter.rb
|
156
157
|
- lib/hanami/model/migrator/connection.rb
|
158
|
+
- lib/hanami/model/migrator/logger.rb
|
157
159
|
- lib/hanami/model/migrator/mysql_adapter.rb
|
158
160
|
- lib/hanami/model/migrator/postgres_adapter.rb
|
159
161
|
- lib/hanami/model/migrator/sqlite_adapter.rb
|
@@ -189,12 +191,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
189
191
|
version: 2.3.0
|
190
192
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
191
193
|
requirements:
|
192
|
-
- - "
|
194
|
+
- - ">"
|
193
195
|
- !ruby/object:Gem::Version
|
194
|
-
version:
|
196
|
+
version: 1.3.1
|
195
197
|
requirements: []
|
196
198
|
rubyforge_project:
|
197
|
-
rubygems_version: 2.6.
|
199
|
+
rubygems_version: 2.6.8
|
198
200
|
signing_key:
|
199
201
|
specification_version: 4
|
200
202
|
summary: A persistence layer for Hanami
|