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.
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