hanami-model 1.2.0 → 1.3.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/LICENSE.md +1 -1
- data/README.md +10 -7
- data/hanami-model.gemspec +25 -20
- data/lib/hanami-model.rb +3 -1
- data/lib/hanami/entity.rb +6 -3
- data/lib/hanami/entity/schema.rb +10 -7
- data/lib/hanami/model.rb +15 -12
- data/lib/hanami/model/association.rb +7 -7
- data/lib/hanami/model/associations/belongs_to.rb +3 -1
- data/lib/hanami/model/associations/dsl.rb +2 -2
- data/lib/hanami/model/associations/has_many.rb +10 -8
- data/lib/hanami/model/associations/has_one.rb +9 -7
- data/lib/hanami/model/associations/many_to_many.rb +9 -11
- data/lib/hanami/model/configuration.rb +29 -10
- data/lib/hanami/model/configurator.rb +5 -3
- data/lib/hanami/model/entity_name.rb +4 -2
- data/lib/hanami/model/error.rb +18 -7
- data/lib/hanami/model/mapped_relation.rb +4 -2
- data/lib/hanami/model/mapping.rb +3 -1
- data/lib/hanami/model/migration.rb +2 -0
- data/lib/hanami/model/migrator.rb +7 -5
- data/lib/hanami/model/migrator/adapter.rb +14 -12
- data/lib/hanami/model/migrator/connection.rb +16 -9
- data/lib/hanami/model/migrator/logger.rb +3 -1
- data/lib/hanami/model/migrator/mysql_adapter.rb +23 -13
- data/lib/hanami/model/migrator/postgres_adapter.rb +31 -31
- data/lib/hanami/model/migrator/sqlite_adapter.rb +7 -9
- data/lib/hanami/model/plugins.rb +5 -3
- data/lib/hanami/model/plugins/mapping.rb +2 -0
- data/lib/hanami/model/plugins/schema.rb +2 -0
- data/lib/hanami/model/plugins/timestamps.rb +3 -0
- data/lib/hanami/model/relation_name.rb +4 -2
- data/lib/hanami/model/sql.rb +9 -7
- data/lib/hanami/model/sql/console.rb +10 -8
- data/lib/hanami/model/sql/consoles/abstract.rb +3 -1
- data/lib/hanami/model/sql/consoles/mysql.rb +4 -2
- data/lib/hanami/model/sql/consoles/postgresql.rb +10 -8
- data/lib/hanami/model/sql/consoles/sqlite.rb +6 -4
- data/lib/hanami/model/sql/entity/schema.rb +6 -4
- data/lib/hanami/model/sql/types.rb +27 -27
- data/lib/hanami/model/sql/types/schema/coercions.rb +5 -4
- data/lib/hanami/model/types.rb +4 -4
- data/lib/hanami/model/version.rb +3 -1
- data/lib/hanami/repository.rb +20 -31
- metadata +64 -8
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "hanami/utils/hash"
|
2
4
|
|
3
5
|
module Hanami
|
@@ -7,7 +9,7 @@ module Hanami
|
|
7
9
|
#
|
8
10
|
# @since 0.7.0
|
9
11
|
# @api private
|
10
|
-
class ManyToMany
|
12
|
+
class ManyToMany
|
11
13
|
# @since 0.7.0
|
12
14
|
# @api private
|
13
15
|
def self.schema_type(entity)
|
@@ -76,8 +78,8 @@ module Hanami
|
|
76
78
|
def add(*data)
|
77
79
|
command(:create, relation(through), use: [:timestamps])
|
78
80
|
.call(associate(serialize(data)))
|
79
|
-
rescue =>
|
80
|
-
raise Hanami::Model::Error.for(
|
81
|
+
rescue => exception
|
82
|
+
raise Hanami::Model::Error.for(exception)
|
81
83
|
end
|
82
84
|
|
83
85
|
# @since 1.1.0
|
@@ -88,18 +90,16 @@ module Hanami
|
|
88
90
|
|
89
91
|
# @since 1.1.0
|
90
92
|
# @api private
|
91
|
-
# rubocop:disable Metrics/AbcSize
|
92
93
|
def remove(target_id)
|
93
94
|
association_record = relation(through)
|
94
|
-
|
95
|
-
|
95
|
+
.where(target_foreign_key => target_id, source_foreign_key => subject.fetch(source_primary_key))
|
96
|
+
.one
|
96
97
|
|
97
98
|
return if association_record.nil?
|
98
99
|
|
99
100
|
ar_id = association_record.public_send relation(through).primary_key
|
100
101
|
command(:delete, relation(through)).by_pk(ar_id).call
|
101
102
|
end
|
102
|
-
# rubocop:enable Metrics/AbcSize
|
103
103
|
|
104
104
|
private
|
105
105
|
|
@@ -172,17 +172,15 @@ module Hanami
|
|
172
172
|
# @since 1.1.0
|
173
173
|
#
|
174
174
|
# @api private
|
175
|
-
# rubocop:disable Metrics/AbcSize
|
176
175
|
def _build_scope
|
177
176
|
result = relation(association.target.to_sym).qualified
|
178
177
|
unless subject.nil?
|
179
178
|
result = result
|
180
|
-
|
181
|
-
|
179
|
+
.join(through, target_foreign_key => target_primary_key)
|
180
|
+
.where(source_foreign_key => subject.fetch(source_primary_key))
|
182
181
|
end
|
183
182
|
result.as(Model::MappedRelation.mapper_name)
|
184
183
|
end
|
185
|
-
# rubocop:enable Metrics/AbcSize
|
186
184
|
|
187
185
|
# @since 1.1.0
|
188
186
|
# @api private
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rom/configuration"
|
2
4
|
|
3
5
|
module Hanami
|
4
6
|
module Model
|
@@ -30,13 +32,13 @@ module Hanami
|
|
30
32
|
def initialize(configurator)
|
31
33
|
@backend = configurator.backend
|
32
34
|
@url = configurator.url
|
33
|
-
@migrations
|
34
|
-
@schema
|
35
|
-
@gateway_config
|
36
|
-
@logger
|
35
|
+
@migrations = configurator._migrations
|
36
|
+
@schema = configurator._schema
|
37
|
+
@gateway_config = configurator._gateway
|
38
|
+
@logger = configurator._logger
|
37
39
|
@migrations_logger = configurator.migrations_logger
|
38
|
-
@mappings
|
39
|
-
@entities
|
40
|
+
@mappings = {}
|
41
|
+
@entities = {}
|
40
42
|
end
|
41
43
|
|
42
44
|
# NOTE: This must be changed when we want to support several adapters at the time
|
@@ -47,6 +49,9 @@ module Hanami
|
|
47
49
|
|
48
50
|
# NOTE: This must be changed when we want to support several adapters at the time
|
49
51
|
#
|
52
|
+
# @raise [Hanami::Model::UnknownDatabaseAdapterError] if `url` is blank,
|
53
|
+
# or it uses an unknown adapter.
|
54
|
+
#
|
50
55
|
# @since 0.7.0
|
51
56
|
# @api private
|
52
57
|
def connection
|
@@ -55,6 +60,9 @@ module Hanami
|
|
55
60
|
|
56
61
|
# NOTE: This must be changed when we want to support several adapters at the time
|
57
62
|
#
|
63
|
+
# @raise [Hanami::Model::UnknownDatabaseAdapterError] if `url` is blank,
|
64
|
+
# or it uses an unknown adapter.
|
65
|
+
#
|
58
66
|
# @since 0.7.0
|
59
67
|
# @api private
|
60
68
|
def gateway
|
@@ -119,18 +127,29 @@ module Hanami
|
|
119
127
|
# @api private
|
120
128
|
def logger=(value)
|
121
129
|
return if value.nil?
|
130
|
+
|
122
131
|
gateway.use_logger(@logger = value)
|
123
132
|
end
|
124
133
|
|
134
|
+
# @raise [Hanami::Model::UnknownDatabaseAdapterError] if `url` is blank,
|
135
|
+
# or it uses an unknown adapter.
|
136
|
+
#
|
125
137
|
# @since 1.0.0
|
126
138
|
# @api private
|
127
139
|
def rom
|
128
140
|
@rom ||= ROM::Configuration.new(@backend, @url, infer_relations: false)
|
141
|
+
rescue => exception
|
142
|
+
raise UnknownDatabaseAdapterError.new(@url) if exception.message =~ /adapters/
|
143
|
+
|
144
|
+
raise exception
|
129
145
|
end
|
130
146
|
|
147
|
+
# @raise [Hanami::Model::UnknownDatabaseAdapterError] if `url` is blank,
|
148
|
+
# or it uses an unknown adapter.
|
149
|
+
#
|
131
150
|
# @since 1.0.0
|
132
151
|
# @api private
|
133
|
-
def load!(repositories, &blk)
|
152
|
+
def load!(repositories, &blk)
|
134
153
|
rom.setup.auto_registration(config.directory.to_s) unless config.directory.nil?
|
135
154
|
rom.instance_eval(&blk) if block_given?
|
136
155
|
configure_gateway
|
@@ -140,8 +159,8 @@ module Hanami
|
|
140
159
|
container = ROM.container(rom)
|
141
160
|
define_entities_mappings(container, repositories)
|
142
161
|
container
|
143
|
-
rescue =>
|
144
|
-
raise Hanami::Model::Error.for(
|
162
|
+
rescue => exception
|
163
|
+
raise Hanami::Model::Error.for(exception)
|
145
164
|
end
|
146
165
|
|
147
166
|
# @since 1.0.0
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Hanami
|
2
4
|
module Model
|
3
5
|
# Configuration DSL
|
@@ -42,7 +44,7 @@ module Hanami
|
|
42
44
|
# @since 1.0.0
|
43
45
|
# @api private
|
44
46
|
def migrations_logger(stream = $stdout)
|
45
|
-
require
|
47
|
+
require "hanami/model/migrator/logger"
|
46
48
|
@migrations_logger ||= Hanami::Model::Migrator::Logger.new(stream)
|
47
49
|
end
|
48
50
|
|
@@ -76,10 +78,10 @@ module Hanami
|
|
76
78
|
# @since 1.0.0
|
77
79
|
# @api private
|
78
80
|
def logger(stream, options = {})
|
79
|
-
require
|
81
|
+
require "hanami/logger"
|
80
82
|
|
81
83
|
opts = options.merge(stream: stream)
|
82
|
-
@_logger = Hanami::Logger.new(
|
84
|
+
@_logger = Hanami::Logger.new("hanami.model", **opts)
|
83
85
|
end
|
84
86
|
|
85
87
|
# @since 1.0.0
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Hanami
|
2
4
|
module Model
|
3
5
|
# Conventional name for entities.
|
@@ -10,7 +12,7 @@ module Hanami
|
|
10
12
|
class EntityName
|
11
13
|
# @since 0.7.0
|
12
14
|
# @api private
|
13
|
-
SUFFIX = /Repository\z
|
15
|
+
SUFFIX = /Repository\z/.freeze
|
14
16
|
|
15
17
|
# @param name [Class,String] the class or its name
|
16
18
|
# @return [String] the entity name
|
@@ -18,7 +20,7 @@ module Hanami
|
|
18
20
|
# @since 0.7.0
|
19
21
|
# @api private
|
20
22
|
def initialize(name)
|
21
|
-
@name = name.sub(SUFFIX,
|
23
|
+
@name = name.sub(SUFFIX, "")
|
22
24
|
end
|
23
25
|
|
24
26
|
# @since 0.7.0
|
data/lib/hanami/model/error.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "concurrent"
|
2
4
|
|
3
5
|
module Hanami
|
4
6
|
module Model
|
@@ -41,7 +43,7 @@ module Hanami
|
|
41
43
|
class InvalidCommandError < Error
|
42
44
|
# @since 0.5.0
|
43
45
|
# @api private
|
44
|
-
def initialize(message =
|
46
|
+
def initialize(message = "Invalid command")
|
45
47
|
super
|
46
48
|
end
|
47
49
|
end
|
@@ -52,7 +54,7 @@ module Hanami
|
|
52
54
|
class ConstraintViolationError < Error
|
53
55
|
# @since 0.7.0
|
54
56
|
# @api private
|
55
|
-
def initialize(message =
|
57
|
+
def initialize(message = "Constraint has been violated")
|
56
58
|
super
|
57
59
|
end
|
58
60
|
end
|
@@ -63,7 +65,7 @@ module Hanami
|
|
63
65
|
class UniqueConstraintViolationError < ConstraintViolationError
|
64
66
|
# @since 0.6.1
|
65
67
|
# @api private
|
66
|
-
def initialize(message =
|
68
|
+
def initialize(message = "Unique constraint has been violated")
|
67
69
|
super
|
68
70
|
end
|
69
71
|
end
|
@@ -74,7 +76,7 @@ module Hanami
|
|
74
76
|
class ForeignKeyConstraintViolationError < ConstraintViolationError
|
75
77
|
# @since 0.6.1
|
76
78
|
# @api private
|
77
|
-
def initialize(message =
|
79
|
+
def initialize(message = "Foreign key constraint has been violated")
|
78
80
|
super
|
79
81
|
end
|
80
82
|
end
|
@@ -85,7 +87,7 @@ module Hanami
|
|
85
87
|
class NotNullConstraintViolationError < ConstraintViolationError
|
86
88
|
# @since 0.6.1
|
87
89
|
# @api private
|
88
|
-
def initialize(message =
|
90
|
+
def initialize(message = "NOT NULL constraint has been violated")
|
89
91
|
super
|
90
92
|
end
|
91
93
|
end
|
@@ -96,7 +98,7 @@ module Hanami
|
|
96
98
|
class CheckConstraintViolationError < ConstraintViolationError
|
97
99
|
# @since 0.6.1
|
98
100
|
# @api private
|
99
|
-
def initialize(message =
|
101
|
+
def initialize(message = "Check constraint has been violated")
|
100
102
|
super
|
101
103
|
end
|
102
104
|
end
|
@@ -118,5 +120,14 @@ module Hanami
|
|
118
120
|
# @since 1.2.0
|
119
121
|
class UnknownAttributeError < Error
|
120
122
|
end
|
123
|
+
|
124
|
+
# Unknown database adapter error
|
125
|
+
#
|
126
|
+
# @since 1.2.1
|
127
|
+
class UnknownDatabaseAdapterError < Error
|
128
|
+
def initialize(url)
|
129
|
+
super("Unknown database adapter for URL: #{url.inspect}. Please check your database configuration (hint: ENV['DATABASE_URL']).")
|
130
|
+
end
|
131
|
+
end
|
121
132
|
end
|
122
133
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Hanami
|
2
4
|
module Model
|
3
5
|
# Mapped proxy for ROM relations.
|
@@ -51,8 +53,8 @@ module Hanami
|
|
51
53
|
# end
|
52
54
|
def [](attribute)
|
53
55
|
@relation[attribute]
|
54
|
-
rescue KeyError =>
|
55
|
-
raise UnknownAttributeError.new(
|
56
|
+
rescue KeyError => exception
|
57
|
+
raise UnknownAttributeError.new(exception.message)
|
56
58
|
end
|
57
59
|
end
|
58
60
|
end
|
data/lib/hanami/model/mapping.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sequel"
|
4
|
+
require "sequel/extensions/migration"
|
3
5
|
|
4
6
|
module Hanami
|
5
7
|
module Model
|
@@ -13,8 +15,8 @@ module Hanami
|
|
13
15
|
#
|
14
16
|
# @since 0.4.0
|
15
17
|
class Migrator
|
16
|
-
require
|
17
|
-
require
|
18
|
+
require "hanami/model/migrator/connection"
|
19
|
+
require "hanami/model/migrator/adapter"
|
18
20
|
|
19
21
|
# Create database defined by current configuration.
|
20
22
|
#
|
@@ -327,7 +329,7 @@ module Hanami
|
|
327
329
|
# @see Hanami::Model::Migrator.prepare
|
328
330
|
def prepare
|
329
331
|
drop
|
330
|
-
rescue # rubocop:disable Lint/
|
332
|
+
rescue # rubocop:disable Lint/SuppressedException
|
331
333
|
ensure
|
332
334
|
create
|
333
335
|
adapter.load
|
@@ -1,6 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "uri"
|
4
|
+
require "shellwords"
|
5
|
+
require "open3"
|
4
6
|
|
5
7
|
module Hanami
|
6
8
|
module Model
|
@@ -26,18 +28,18 @@ module Hanami
|
|
26
28
|
#
|
27
29
|
# @since 0.4.0
|
28
30
|
# @api private
|
29
|
-
def self.for(configuration)
|
31
|
+
def self.for(configuration)
|
30
32
|
connection = Connection.new(configuration)
|
31
33
|
|
32
34
|
case connection.database_type
|
33
35
|
when :sqlite
|
34
|
-
require
|
36
|
+
require "hanami/model/migrator/sqlite_adapter"
|
35
37
|
SQLiteAdapter
|
36
38
|
when :postgres
|
37
|
-
require
|
39
|
+
require "hanami/model/migrator/postgres_adapter"
|
38
40
|
PostgresAdapter
|
39
41
|
when :mysql
|
40
|
-
require
|
42
|
+
require "hanami/model/migrator/mysql_adapter"
|
41
43
|
MySQLAdapter
|
42
44
|
else
|
43
45
|
self
|
@@ -80,8 +82,8 @@ module Hanami
|
|
80
82
|
version = Integer(version) unless version.nil?
|
81
83
|
|
82
84
|
Sequel::Migrator.run(connection.raw, migrations, target: version, allow_missing_migration_files: true)
|
83
|
-
rescue Sequel::Migrator::Error =>
|
84
|
-
raise MigrationError.new(
|
85
|
+
rescue Sequel::Migrator::Error => exception
|
86
|
+
raise MigrationError.new(exception.message)
|
85
87
|
end
|
86
88
|
|
87
89
|
# @since 1.1.0
|
@@ -91,8 +93,8 @@ module Hanami
|
|
91
93
|
version = version_to_rollback(table, steps)
|
92
94
|
|
93
95
|
Sequel::Migrator.run(connection.raw, migrations, target: version, allow_missing_migration_files: true)
|
94
|
-
rescue Sequel::Migrator::Error =>
|
95
|
-
raise MigrationError.new(
|
96
|
+
rescue Sequel::Migrator::Error => exception
|
97
|
+
raise MigrationError.new(exception.message)
|
96
98
|
end
|
97
99
|
|
98
100
|
# Load database schema.
|
@@ -124,7 +126,7 @@ module Hanami
|
|
124
126
|
|
125
127
|
# @since 1.1.0
|
126
128
|
# @api private
|
127
|
-
MIGRATIONS_FILE_NAME_PATTERN = /\A[\d]{14}
|
129
|
+
MIGRATIONS_FILE_NAME_PATTERN = /\A[\d]{14}/.freeze
|
128
130
|
|
129
131
|
# @since 1.1.0
|
130
132
|
# @api private
|
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cgi"
|
4
|
+
|
1
5
|
module Hanami
|
2
6
|
module Model
|
3
7
|
class Migrator
|
@@ -34,7 +38,7 @@ module Hanami
|
|
34
38
|
# @since 0.5.0
|
35
39
|
# @api private
|
36
40
|
def host
|
37
|
-
@host ||= parsed_uri.host || parsed_opt(
|
41
|
+
@host ||= parsed_uri.host || parsed_opt("host")
|
38
42
|
end
|
39
43
|
|
40
44
|
# Returns DB connection port
|
@@ -44,7 +48,7 @@ module Hanami
|
|
44
48
|
# @since 0.5.0
|
45
49
|
# @api private
|
46
50
|
def port
|
47
|
-
@port ||= parsed_uri.port || parsed_opt(
|
51
|
+
@port ||= parsed_uri.port || parsed_opt("port").to_i.nonzero?
|
48
52
|
end
|
49
53
|
|
50
54
|
# Returns DB name from conenction
|
@@ -83,7 +87,7 @@ module Hanami
|
|
83
87
|
# @since 0.5.0
|
84
88
|
# @api private
|
85
89
|
def user
|
86
|
-
@user ||= parsed_opt(
|
90
|
+
@user ||= parsed_opt("user") || parsed_uri.user
|
87
91
|
end
|
88
92
|
|
89
93
|
# Returns user from DB connection
|
@@ -93,7 +97,7 @@ module Hanami
|
|
93
97
|
# @since 0.5.0
|
94
98
|
# @api private
|
95
99
|
def password
|
96
|
-
@password ||= parsed_opt(
|
100
|
+
@password ||= parsed_opt("password") || parsed_uri.password
|
97
101
|
end
|
98
102
|
|
99
103
|
# Returns DB connection URI directly from adapter
|
@@ -109,7 +113,7 @@ module Hanami
|
|
109
113
|
# @since 0.5.0
|
110
114
|
# @api private
|
111
115
|
def global_uri
|
112
|
-
uri.sub(parsed_uri.select(:path).first,
|
116
|
+
uri.sub(parsed_uri.select(:path).first, "")
|
113
117
|
end
|
114
118
|
|
115
119
|
# Returns a boolean telling if a DB connection is from JDBC or not
|
@@ -117,7 +121,7 @@ module Hanami
|
|
117
121
|
# @since 0.5.0
|
118
122
|
# @api private
|
119
123
|
def jdbc?
|
120
|
-
!uri.scan(
|
124
|
+
!uri.scan("jdbc:").empty?
|
121
125
|
end
|
122
126
|
|
123
127
|
# Returns database connection URI instance without JDBC namespace
|
@@ -125,7 +129,7 @@ module Hanami
|
|
125
129
|
# @since 0.5.0
|
126
130
|
# @api private
|
127
131
|
def parsed_uri
|
128
|
-
@parsed_uri ||= URI.parse(uri.sub(
|
132
|
+
@parsed_uri ||= URI.parse(uri.sub("jdbc:", ""))
|
129
133
|
end
|
130
134
|
|
131
135
|
# @api private
|
@@ -153,8 +157,11 @@ module Hanami
|
|
153
157
|
#
|
154
158
|
# @since 0.5.0
|
155
159
|
# @api private
|
156
|
-
def parsed_opt(option)
|
157
|
-
|
160
|
+
def parsed_opt(option, query: parsed_uri.query)
|
161
|
+
return if query.nil?
|
162
|
+
|
163
|
+
@parsed_query_opts ||= CGI.parse(query)
|
164
|
+
@parsed_query_opts[option].to_a.last
|
158
165
|
end
|
159
166
|
end
|
160
167
|
end
|