hanami-model 0.7.0 → 1.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|