hanami-model 1.2.0 → 1.3.3

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +10 -7
  5. data/hanami-model.gemspec +25 -20
  6. data/lib/hanami-model.rb +3 -1
  7. data/lib/hanami/entity.rb +6 -3
  8. data/lib/hanami/entity/schema.rb +10 -7
  9. data/lib/hanami/model.rb +15 -12
  10. data/lib/hanami/model/association.rb +7 -7
  11. data/lib/hanami/model/associations/belongs_to.rb +3 -1
  12. data/lib/hanami/model/associations/dsl.rb +2 -2
  13. data/lib/hanami/model/associations/has_many.rb +10 -8
  14. data/lib/hanami/model/associations/has_one.rb +9 -7
  15. data/lib/hanami/model/associations/many_to_many.rb +9 -11
  16. data/lib/hanami/model/configuration.rb +29 -10
  17. data/lib/hanami/model/configurator.rb +5 -3
  18. data/lib/hanami/model/entity_name.rb +4 -2
  19. data/lib/hanami/model/error.rb +18 -7
  20. data/lib/hanami/model/mapped_relation.rb +4 -2
  21. data/lib/hanami/model/mapping.rb +3 -1
  22. data/lib/hanami/model/migration.rb +2 -0
  23. data/lib/hanami/model/migrator.rb +7 -5
  24. data/lib/hanami/model/migrator/adapter.rb +14 -12
  25. data/lib/hanami/model/migrator/connection.rb +16 -9
  26. data/lib/hanami/model/migrator/logger.rb +3 -1
  27. data/lib/hanami/model/migrator/mysql_adapter.rb +23 -13
  28. data/lib/hanami/model/migrator/postgres_adapter.rb +31 -31
  29. data/lib/hanami/model/migrator/sqlite_adapter.rb +7 -9
  30. data/lib/hanami/model/plugins.rb +5 -3
  31. data/lib/hanami/model/plugins/mapping.rb +2 -0
  32. data/lib/hanami/model/plugins/schema.rb +2 -0
  33. data/lib/hanami/model/plugins/timestamps.rb +3 -0
  34. data/lib/hanami/model/relation_name.rb +4 -2
  35. data/lib/hanami/model/sql.rb +9 -7
  36. data/lib/hanami/model/sql/console.rb +10 -8
  37. data/lib/hanami/model/sql/consoles/abstract.rb +3 -1
  38. data/lib/hanami/model/sql/consoles/mysql.rb +4 -2
  39. data/lib/hanami/model/sql/consoles/postgresql.rb +10 -8
  40. data/lib/hanami/model/sql/consoles/sqlite.rb +6 -4
  41. data/lib/hanami/model/sql/entity/schema.rb +6 -4
  42. data/lib/hanami/model/sql/types.rb +27 -27
  43. data/lib/hanami/model/sql/types/schema/coercions.rb +5 -4
  44. data/lib/hanami/model/types.rb +4 -4
  45. data/lib/hanami/model/version.rb +3 -1
  46. data/lib/hanami/repository.rb +20 -31
  47. 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 # rubocop:disable Metrics/ClassLength
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 => e
80
- raise Hanami::Model::Error.for(e)
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
- .where(target_foreign_key => target_id, source_foreign_key => subject.fetch(source_primary_key))
95
- .one
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
- .join(through, target_foreign_key => target_primary_key)
181
- .where(source_foreign_key => subject.fetch(source_primary_key))
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
- require 'rom/configuration'
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 = configurator._migrations
34
- @schema = configurator._schema
35
- @gateway_config = configurator._gateway
36
- @logger = configurator._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) # rubocop:disable Metrics/AbcSize
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 => e
144
- raise Hanami::Model::Error.for(e)
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 'hanami/model/migrator/logger'
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 'hanami/logger'
81
+ require "hanami/logger"
80
82
 
81
83
  opts = options.merge(stream: stream)
82
- @_logger = Hanami::Logger.new('hanami.model', opts)
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
@@ -1,4 +1,6 @@
1
- require 'concurrent'
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 = 'Invalid command')
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 = 'Constraint has been violated')
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 = 'Unique constraint has been violated')
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 = 'Foreign key constraint has been violated')
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 = 'NOT NULL constraint has been violated')
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 = 'Check constraint has been violated')
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 => e
55
- raise UnknownAttributeError.new(e.message)
56
+ rescue KeyError => exception
57
+ raise UnknownAttributeError.new(exception.message)
56
58
  end
57
59
  end
58
60
  end
@@ -1,4 +1,6 @@
1
- require 'transproc/all'
1
+ # frozen_string_literal: true
2
+
3
+ require "transproc/all"
2
4
 
3
5
  module Hanami
4
6
  module Model
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hanami
2
4
  module Model
3
5
  # Database migration
@@ -1,5 +1,7 @@
1
- require 'sequel'
2
- require 'sequel/extensions/migration'
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 'hanami/model/migrator/connection'
17
- require 'hanami/model/migrator/adapter'
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/HandleExceptions
332
+ rescue # rubocop:disable Lint/SuppressedException
331
333
  ensure
332
334
  create
333
335
  adapter.load
@@ -1,6 +1,8 @@
1
- require 'uri'
2
- require 'shellwords'
3
- require 'open3'
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) # rubocop:disable Metrics/MethodLength
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 'hanami/model/migrator/sqlite_adapter'
36
+ require "hanami/model/migrator/sqlite_adapter"
35
37
  SQLiteAdapter
36
38
  when :postgres
37
- require 'hanami/model/migrator/postgres_adapter'
39
+ require "hanami/model/migrator/postgres_adapter"
38
40
  PostgresAdapter
39
41
  when :mysql
40
- require 'hanami/model/migrator/mysql_adapter'
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 => e
84
- raise MigrationError.new(e.message)
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 => e
95
- raise MigrationError.new(e.message)
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('host')
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('port').to_i.nonzero?
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('user') || parsed_uri.user
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('password') || parsed_uri.password
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('jdbc:').empty?
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('jdbc:', ''))
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
- parsed_uri.to_s.match(/[\?|\&]#{ option }=(\w+)\&?/).to_a.last
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