hanami-model 0.6.1 → 0.7.0

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -0
  3. data/README.md +54 -420
  4. data/hanami-model.gemspec +9 -6
  5. data/lib/hanami/entity.rb +107 -191
  6. data/lib/hanami/entity/schema.rb +236 -0
  7. data/lib/hanami/model.rb +52 -138
  8. data/lib/hanami/model/association.rb +37 -0
  9. data/lib/hanami/model/associations/belongs_to.rb +19 -0
  10. data/lib/hanami/model/associations/dsl.rb +29 -0
  11. data/lib/hanami/model/associations/has_many.rb +200 -0
  12. data/lib/hanami/model/configuration.rb +52 -224
  13. data/lib/hanami/model/configurator.rb +62 -0
  14. data/lib/hanami/model/entity_name.rb +35 -0
  15. data/lib/hanami/model/error.rb +37 -24
  16. data/lib/hanami/model/mapping.rb +29 -35
  17. data/lib/hanami/model/migration.rb +31 -0
  18. data/lib/hanami/model/migrator.rb +111 -88
  19. data/lib/hanami/model/migrator/adapter.rb +39 -16
  20. data/lib/hanami/model/migrator/connection.rb +23 -11
  21. data/lib/hanami/model/migrator/mysql_adapter.rb +38 -17
  22. data/lib/hanami/model/migrator/postgres_adapter.rb +20 -19
  23. data/lib/hanami/model/migrator/sqlite_adapter.rb +9 -8
  24. data/lib/hanami/model/plugins.rb +25 -0
  25. data/lib/hanami/model/plugins/mapping.rb +55 -0
  26. data/lib/hanami/model/plugins/schema.rb +55 -0
  27. data/lib/hanami/model/plugins/timestamps.rb +118 -0
  28. data/lib/hanami/model/relation_name.rb +24 -0
  29. data/lib/hanami/model/sql.rb +161 -0
  30. data/lib/hanami/model/sql/console.rb +41 -0
  31. data/lib/hanami/model/sql/consoles/abstract.rb +33 -0
  32. data/lib/hanami/model/sql/consoles/mysql.rb +63 -0
  33. data/lib/hanami/model/sql/consoles/postgresql.rb +68 -0
  34. data/lib/hanami/model/sql/consoles/sqlite.rb +46 -0
  35. data/lib/hanami/model/sql/entity/schema.rb +125 -0
  36. data/lib/hanami/model/sql/types.rb +95 -0
  37. data/lib/hanami/model/sql/types/schema/coercions.rb +198 -0
  38. data/lib/hanami/model/types.rb +99 -0
  39. data/lib/hanami/model/version.rb +1 -1
  40. data/lib/hanami/repository.rb +287 -723
  41. metadata +77 -40
  42. data/EXAMPLE.md +0 -213
  43. data/lib/hanami/entity/dirty_tracking.rb +0 -74
  44. data/lib/hanami/model/adapters/abstract.rb +0 -281
  45. data/lib/hanami/model/adapters/file_system_adapter.rb +0 -288
  46. data/lib/hanami/model/adapters/implementation.rb +0 -111
  47. data/lib/hanami/model/adapters/memory/collection.rb +0 -132
  48. data/lib/hanami/model/adapters/memory/command.rb +0 -113
  49. data/lib/hanami/model/adapters/memory/query.rb +0 -653
  50. data/lib/hanami/model/adapters/memory_adapter.rb +0 -179
  51. data/lib/hanami/model/adapters/null_adapter.rb +0 -24
  52. data/lib/hanami/model/adapters/sql/collection.rb +0 -287
  53. data/lib/hanami/model/adapters/sql/command.rb +0 -88
  54. data/lib/hanami/model/adapters/sql/console.rb +0 -33
  55. data/lib/hanami/model/adapters/sql/consoles/mysql.rb +0 -49
  56. data/lib/hanami/model/adapters/sql/consoles/postgresql.rb +0 -48
  57. data/lib/hanami/model/adapters/sql/consoles/sqlite.rb +0 -26
  58. data/lib/hanami/model/adapters/sql/query.rb +0 -788
  59. data/lib/hanami/model/adapters/sql_adapter.rb +0 -296
  60. data/lib/hanami/model/coercer.rb +0 -74
  61. data/lib/hanami/model/config/adapter.rb +0 -116
  62. data/lib/hanami/model/config/mapper.rb +0 -45
  63. data/lib/hanami/model/mapper.rb +0 -124
  64. data/lib/hanami/model/mapping/attribute.rb +0 -85
  65. data/lib/hanami/model/mapping/coercers.rb +0 -314
  66. data/lib/hanami/model/mapping/collection.rb +0 -490
  67. data/lib/hanami/model/mapping/collection_coercer.rb +0 -79
@@ -1,6 +1,6 @@
1
1
  module Hanami
2
2
  module Model
3
- module Migrator
3
+ class Migrator
4
4
  # Sequel connection wrapper
5
5
  #
6
6
  # Normalize external adapters interfaces
@@ -8,10 +8,14 @@ module Hanami
8
8
  # @since 0.5.0
9
9
  # @api private
10
10
  class Connection
11
- attr_reader :adapter_connection
11
+ # @since 0.7.0
12
+ # @api private
13
+ attr_reader :raw
12
14
 
13
- def initialize(adapter_connection)
14
- @adapter_connection = adapter_connection
15
+ # @since 0.5.0
16
+ # @api private
17
+ def initialize(raw)
18
+ @raw = raw
15
19
  end
16
20
 
17
21
  # Returns DB connection host
@@ -48,12 +52,12 @@ module Hanami
48
52
  #
49
53
  # @example
50
54
  # connection.database_type
51
- # # => 'postgres'
55
+ # # => 'postgres'
52
56
  #
53
57
  # @since 0.5.0
54
58
  # @api private
55
59
  def database_type
56
- adapter_connection.database_type
60
+ raw.database_type
57
61
  end
58
62
 
59
63
  # Returns user from DB connection
@@ -81,7 +85,7 @@ module Hanami
81
85
  # @since 0.5.0
82
86
  # @api private
83
87
  def uri
84
- adapter_connection.uri
88
+ raw.uri
85
89
  end
86
90
 
87
91
  # Returns DB connection wihout specifying database name
@@ -89,7 +93,7 @@ module Hanami
89
93
  # @since 0.5.0
90
94
  # @api private
91
95
  def global_uri
92
- adapter_connection.uri.sub(parsed_uri.select(:path).first, '')
96
+ raw.uri.sub(parsed_uri.select(:path).first, '')
93
97
  end
94
98
 
95
99
  # Returns a boolean telling if a DB connection is from JDBC or not
@@ -97,7 +101,7 @@ module Hanami
97
101
  # @since 0.5.0
98
102
  # @api private
99
103
  def jdbc?
100
- !adapter_connection.uri.scan('jdbc:').empty?
104
+ !raw.uri.scan('jdbc:').empty?
101
105
  end
102
106
 
103
107
  # Returns database connection URI instance without JDBC namespace
@@ -105,7 +109,15 @@ module Hanami
105
109
  # @since 0.5.0
106
110
  # @api private
107
111
  def parsed_uri
108
- @uri ||= URI.parse(adapter_connection.uri.sub('jdbc:', ''))
112
+ @uri ||= URI.parse(raw.uri.sub('jdbc:', ''))
113
+ end
114
+
115
+ # Return the database table for the given name
116
+ #
117
+ # @since 0.7.0
118
+ # @api private
119
+ def table(name)
120
+ raw[name] if raw.tables.include?(name)
109
121
  end
110
122
 
111
123
  private
@@ -125,7 +137,7 @@ module Hanami
125
137
  # @since 0.5.0
126
138
  # @api private
127
139
  def opts
128
- adapter_connection.opts
140
+ raw.opts
129
141
  end
130
142
  end
131
143
  end
@@ -1,21 +1,28 @@
1
1
  module Hanami
2
2
  module Model
3
- module Migrator
3
+ class Migrator
4
4
  # MySQL adapter
5
5
  #
6
6
  # @since 0.4.0
7
7
  # @api private
8
8
  class MySQLAdapter < Adapter
9
+ # @since 0.7.0
10
+ # @api private
11
+ PASSWORD = 'MYSQL_PWD'.freeze
12
+
9
13
  # @since 0.4.0
10
14
  # @api private
11
- def create
12
- new_connection(global: true).run %(CREATE DATABASE #{ database };)
15
+ def create # rubocop:disable Metrics/MethodLength
16
+ new_connection(global: true).run %(CREATE DATABASE #{database};)
13
17
  rescue Sequel::DatabaseError => e
14
- message = if e.message.match(/database exists/)
15
- "Database creation failed. There is 1 other session using the database"
16
- else
17
- e.message
18
- end
18
+ message = if e.message.match(/database exists/) # rubocop:disable Performance/RedundantMatch
19
+ "Database creation failed. If the database exists, \
20
+ then its console may be open. See this issue for more details:\
21
+ https://github.com/hanami/model/issues/250\
22
+ "
23
+ else
24
+ e.message
25
+ end
19
26
 
20
27
  raise MigrationError.new(message)
21
28
  end
@@ -23,13 +30,13 @@ module Hanami
23
30
  # @since 0.4.0
24
31
  # @api private
25
32
  def drop
26
- new_connection(global: true).run %(DROP DATABASE #{ database };)
33
+ new_connection(global: true).run %(DROP DATABASE #{database};)
27
34
  rescue Sequel::DatabaseError => e
28
- message = if e.message.match(/doesn\'t exist/)
29
- "Cannot find database: #{ database }"
30
- else
31
- e.message
32
- end
35
+ message = if e.message.match(/doesn\'t exist/) # rubocop:disable Performance/RedundantMatch
36
+ "Cannot find database: #{database}"
37
+ else
38
+ e.message
39
+ end
33
40
 
34
41
  raise MigrationError.new(message)
35
42
  end
@@ -37,6 +44,7 @@ module Hanami
37
44
  # @since 0.4.0
38
45
  # @api private
39
46
  def dump
47
+ set_environment_variables
40
48
  dump_structure
41
49
  dump_migrations_data
42
50
  end
@@ -44,27 +52,40 @@ module Hanami
44
52
  # @since 0.4.0
45
53
  # @api private
46
54
  def load
55
+ set_environment_variables
47
56
  load_structure
48
57
  end
49
58
 
50
59
  private
51
60
 
61
+ # @since 0.7.0
62
+ # @api private
63
+ def set_environment_variables
64
+ ENV[PASSWORD] = password unless password.nil?
65
+ end
66
+
67
+ # @since 0.7.0
68
+ # @api private
69
+ def password
70
+ connection.password
71
+ end
72
+
52
73
  # @since 0.4.0
53
74
  # @api private
54
75
  def dump_structure
55
- system "mysqldump --user=#{ username } --password=#{ password } --no-data --skip-comments --ignore-table=#{ database }.#{ migrations_table } #{ database } > #{ schema }"
76
+ system "mysqldump --user=#{username} --no-data --skip-comments --ignore-table=#{database}.#{migrations_table} #{database} > #{schema}"
56
77
  end
57
78
 
58
79
  # @since 0.4.0
59
80
  # @api private
60
81
  def load_structure
61
- system "mysql --user=#{ username } --password=#{ password } #{ database } < #{ escape(schema) }" if schema.exist?
82
+ system "mysql --user=#{username} #{database} < #{escape(schema)}" if schema.exist?
62
83
  end
63
84
 
64
85
  # @since 0.4.0
65
86
  # @api private
66
87
  def dump_migrations_data
67
- system "mysqldump --user=#{ username } --password=#{ password } --skip-comments #{ database } #{ migrations_table } >> #{ schema }"
88
+ system "mysqldump --user=#{username} --skip-comments #{database} #{migrations_table} >> #{schema}"
68
89
  end
69
90
  end
70
91
  end
@@ -1,6 +1,6 @@
1
1
  module Hanami
2
2
  module Model
3
- module Migrator
3
+ class Migrator
4
4
  # PostgreSQL adapter
5
5
  #
6
6
  # @since 0.4.0
@@ -24,15 +24,18 @@ module Hanami
24
24
 
25
25
  # @since 0.4.0
26
26
  # @api private
27
- def create
27
+ def create # rubocop:disable Metrics/MethodLength
28
28
  set_environment_variables
29
29
 
30
30
  call_db_command('createdb') do |error_message|
31
- message = if error_message.match(/already exists/)
32
- "createdb: database creation failed. There is 1 other session using the database."
33
- else
34
- error_message
35
- end
31
+ message = if error_message.match(/already exists/) # rubocop:disable Performance/RedundantMatch
32
+ "createdb: database creation failed. If the database exists, \
33
+ then its console may be open. See this issue for more details:\
34
+ https://github.com/hanami/model/issues/250\
35
+ "
36
+ else
37
+ error_message
38
+ end
36
39
 
37
40
  raise MigrationError.new(message)
38
41
  end
@@ -44,11 +47,11 @@ module Hanami
44
47
  set_environment_variables
45
48
 
46
49
  call_db_command('dropdb') do |error_message|
47
- message = if error_message.match(/does not exist/)
48
- "Cannot find database: #{ database }"
49
- else
50
- error_message
51
- end
50
+ message = if error_message.match(/does not exist/) # rubocop:disable Performance/RedundantMatch
51
+ "Cannot find database: #{database}"
52
+ else
53
+ error_message
54
+ end
52
55
 
53
56
  raise MigrationError.new(message)
54
57
  end
@@ -83,19 +86,19 @@ module Hanami
83
86
  # @since 0.4.0
84
87
  # @api private
85
88
  def dump_structure
86
- system "pg_dump -i -s -x -O -T #{ migrations_table } -f #{ escape(schema) } #{ database }"
89
+ system "pg_dump -s -x -O -T #{migrations_table} -f #{escape(schema)} #{database}"
87
90
  end
88
91
 
89
92
  # @since 0.4.0
90
93
  # @api private
91
94
  def load_structure
92
- system "psql -X -q -f #{ escape(schema) } #{ database }" if schema.exist?
95
+ system "psql -X -q -f #{escape(schema)} #{database}" if schema.exist?
93
96
  end
94
97
 
95
98
  # @since 0.4.0
96
99
  # @api private
97
100
  def dump_migrations_data
98
- system "pg_dump -t #{ migrations_table } #{ database } >> #{ escape(schema) }"
101
+ system "pg_dump -t #{migrations_table} #{database} >> #{escape(schema)}"
99
102
  end
100
103
 
101
104
  # @since 0.5.1
@@ -104,10 +107,8 @@ module Hanami
104
107
  require 'open3'
105
108
 
106
109
  begin
107
- Open3.popen3(command, database) do |stdin, stdout, stderr, wait_thr|
108
- unless wait_thr.value.success? # wait_thr.value is the exit status
109
- yield stderr.read
110
- end
110
+ Open3.popen3(command, database) do |_stdin, _stdout, stderr, wait_thr|
111
+ yield stderr.read unless wait_thr.value.success? # wait_thr.value is the exit status
111
112
  end
112
113
  rescue SystemCallError => e
113
114
  yield e.message
@@ -1,8 +1,9 @@
1
1
  require 'pathname'
2
+ require 'hanami/utils'
2
3
 
3
4
  module Hanami
4
5
  module Model
5
- module Migrator
6
+ class Migrator
6
7
  # SQLite3 Migrator
7
8
  #
8
9
  # @since 0.4.0
@@ -28,7 +29,7 @@ module Hanami
28
29
  #
29
30
  # @since 0.4.0
30
31
  # @api private
31
- def initialize(connection)
32
+ def initialize(connection, configuration)
32
33
  super
33
34
  extend Memory if memory?
34
35
  end
@@ -39,7 +40,7 @@ module Hanami
39
40
  path.dirname.mkpath
40
41
  FileUtils.touch(path)
41
42
  rescue Errno::EACCES, Errno::EPERM
42
- raise MigrationError.new("Permission denied: #{ path.sub(/\A\/\//, '') }")
43
+ raise MigrationError.new("Permission denied: #{path.sub(/\A\/\//, '')}")
43
44
  end
44
45
 
45
46
  # @since 0.4.0
@@ -47,7 +48,7 @@ module Hanami
47
48
  def drop
48
49
  path.delete
49
50
  rescue Errno::ENOENT
50
- raise MigrationError.new("Cannot find database: #{ path.sub(/\A\/\//, '') }")
51
+ raise MigrationError.new("Cannot find database: #{path.sub(/\A\/\//, '')}")
51
52
  end
52
53
 
53
54
  # @since 0.4.0
@@ -69,7 +70,7 @@ module Hanami
69
70
  # @api private
70
71
  def path
71
72
  root.join(
72
- @connection.uri.sub(/(jdbc\:|)sqlite\:\/\//, '')
73
+ @connection.uri.sub(/\A(jdbc:sqlite:|sqlite:\/\/)/, '')
73
74
  )
74
75
  end
75
76
 
@@ -90,19 +91,19 @@ module Hanami
90
91
  # @since 0.4.0
91
92
  # @api private
92
93
  def dump_structure
93
- system "sqlite3 #{ escape(path) } .schema > #{ escape(schema) }"
94
+ system "sqlite3 #{escape(path)} .schema > #{escape(schema)}"
94
95
  end
95
96
 
96
97
  # @since 0.4.0
97
98
  # @api private
98
99
  def load_structure
99
- system "sqlite3 #{ escape(path) } < #{ escape(schema) }" if schema.exist?
100
+ system "sqlite3 #{escape(path)} < #{escape(schema)}" if schema.exist?
100
101
  end
101
102
 
102
103
  # @since 0.4.0
103
104
  # @api private
104
105
  def dump_migrations_data
105
- system %(sqlite3 #{ escape(path) } .dump | grep '^INSERT INTO "#{ migrations_table }"' >> #{ escape(schema) })
106
+ system %(sqlite3 #{escape(path)} .dump | grep '^INSERT INTO "#{migrations_table}"' >> #{escape(schema)})
106
107
  end
107
108
  end
108
109
  end
@@ -0,0 +1,25 @@
1
+ module Hanami
2
+ module Model
3
+ # Plugins to extend read/write operations from/to the database
4
+ #
5
+ # @since 0.7.0
6
+ # @api private
7
+ module Plugins
8
+ # Wrapping input
9
+ #
10
+ # @since 0.7.0
11
+ # @api private
12
+ class WrappingInput
13
+ # @since 0.7.0
14
+ # @api private
15
+ def initialize(_relation, input)
16
+ @input = input || Hash
17
+ end
18
+ end
19
+
20
+ require 'hanami/model/plugins/mapping'
21
+ require 'hanami/model/plugins/schema'
22
+ require 'hanami/model/plugins/timestamps'
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,55 @@
1
+ module Hanami
2
+ module Model
3
+ module Plugins
4
+ # Transform output into model domain types (entities).
5
+ #
6
+ # @since 0.7.0
7
+ # @api private
8
+ module Mapping
9
+ # Takes the output and applies the transformations
10
+ #
11
+ # @since 0.7.0
12
+ # @api private
13
+ class InputWithMapping < WrappingInput
14
+ # @since 0.7.0
15
+ # @api private
16
+ def initialize(relation, input)
17
+ super
18
+ @mapping = Hanami::Model.configuration.mappings[relation.name.to_sym]
19
+ end
20
+
21
+ # Processes the output
22
+ #
23
+ # @since 0.7.0
24
+ # @api private
25
+ def [](value)
26
+ @mapping.process(@input[value])
27
+ end
28
+ end
29
+
30
+ # Class interface
31
+ #
32
+ # @since 0.7.0
33
+ # @api private
34
+ module ClassMethods
35
+ # Builds the output processor
36
+ #
37
+ # @since 0.7.0
38
+ # @api private
39
+ def build(relation, options = {})
40
+ input(InputWithMapping.new(relation, input))
41
+ super(relation, options.merge(input: input))
42
+ end
43
+ end
44
+
45
+ # @since 0.7.0
46
+ # @api private
47
+ def self.included(klass)
48
+ super
49
+
50
+ klass.extend ClassMethods
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,55 @@
1
+ module Hanami
2
+ module Model
3
+ module Plugins
4
+ # Transform input values into database specific types (primitives).
5
+ #
6
+ # @since 0.7.0
7
+ # @api private
8
+ module Schema
9
+ # Takes the input and applies the values transformations.
10
+ #
11
+ # @since 0.7.0
12
+ # @api private
13
+ class InputWithSchema < WrappingInput
14
+ # @since 0.7.0
15
+ # @api private
16
+ def initialize(relation, input)
17
+ super
18
+ @schema = relation.schema_hash
19
+ end
20
+
21
+ # Processes the input
22
+ #
23
+ # @since 0.7.0
24
+ # @api private
25
+ def [](value)
26
+ @schema[@input[value]]
27
+ end
28
+ end
29
+
30
+ # Class interface
31
+ #
32
+ # @since 0.7.0
33
+ # @api private
34
+ module ClassMethods
35
+ # Builds the input processor
36
+ #
37
+ # @since 0.7.0
38
+ # @api private
39
+ def build(relation, options = {})
40
+ input(InputWithSchema.new(relation, input))
41
+ super(relation, options.merge(input: input))
42
+ end
43
+ end
44
+
45
+ # @since 0.7.0
46
+ # @api private
47
+ def self.included(klass)
48
+ super
49
+
50
+ klass.extend ClassMethods
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end