hanami-model 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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