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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +44 -0
- data/README.md +54 -420
- data/hanami-model.gemspec +9 -6
- data/lib/hanami/entity.rb +107 -191
- data/lib/hanami/entity/schema.rb +236 -0
- data/lib/hanami/model.rb +52 -138
- data/lib/hanami/model/association.rb +37 -0
- data/lib/hanami/model/associations/belongs_to.rb +19 -0
- data/lib/hanami/model/associations/dsl.rb +29 -0
- data/lib/hanami/model/associations/has_many.rb +200 -0
- data/lib/hanami/model/configuration.rb +52 -224
- data/lib/hanami/model/configurator.rb +62 -0
- data/lib/hanami/model/entity_name.rb +35 -0
- data/lib/hanami/model/error.rb +37 -24
- data/lib/hanami/model/mapping.rb +29 -35
- data/lib/hanami/model/migration.rb +31 -0
- data/lib/hanami/model/migrator.rb +111 -88
- data/lib/hanami/model/migrator/adapter.rb +39 -16
- data/lib/hanami/model/migrator/connection.rb +23 -11
- data/lib/hanami/model/migrator/mysql_adapter.rb +38 -17
- data/lib/hanami/model/migrator/postgres_adapter.rb +20 -19
- data/lib/hanami/model/migrator/sqlite_adapter.rb +9 -8
- data/lib/hanami/model/plugins.rb +25 -0
- data/lib/hanami/model/plugins/mapping.rb +55 -0
- data/lib/hanami/model/plugins/schema.rb +55 -0
- data/lib/hanami/model/plugins/timestamps.rb +118 -0
- data/lib/hanami/model/relation_name.rb +24 -0
- data/lib/hanami/model/sql.rb +161 -0
- data/lib/hanami/model/sql/console.rb +41 -0
- data/lib/hanami/model/sql/consoles/abstract.rb +33 -0
- data/lib/hanami/model/sql/consoles/mysql.rb +63 -0
- data/lib/hanami/model/sql/consoles/postgresql.rb +68 -0
- data/lib/hanami/model/sql/consoles/sqlite.rb +46 -0
- data/lib/hanami/model/sql/entity/schema.rb +125 -0
- data/lib/hanami/model/sql/types.rb +95 -0
- data/lib/hanami/model/sql/types/schema/coercions.rb +198 -0
- data/lib/hanami/model/types.rb +99 -0
- data/lib/hanami/model/version.rb +1 -1
- data/lib/hanami/repository.rb +287 -723
- metadata +77 -40
- data/EXAMPLE.md +0 -213
- data/lib/hanami/entity/dirty_tracking.rb +0 -74
- data/lib/hanami/model/adapters/abstract.rb +0 -281
- data/lib/hanami/model/adapters/file_system_adapter.rb +0 -288
- data/lib/hanami/model/adapters/implementation.rb +0 -111
- data/lib/hanami/model/adapters/memory/collection.rb +0 -132
- data/lib/hanami/model/adapters/memory/command.rb +0 -113
- data/lib/hanami/model/adapters/memory/query.rb +0 -653
- data/lib/hanami/model/adapters/memory_adapter.rb +0 -179
- data/lib/hanami/model/adapters/null_adapter.rb +0 -24
- data/lib/hanami/model/adapters/sql/collection.rb +0 -287
- data/lib/hanami/model/adapters/sql/command.rb +0 -88
- data/lib/hanami/model/adapters/sql/console.rb +0 -33
- data/lib/hanami/model/adapters/sql/consoles/mysql.rb +0 -49
- data/lib/hanami/model/adapters/sql/consoles/postgresql.rb +0 -48
- data/lib/hanami/model/adapters/sql/consoles/sqlite.rb +0 -26
- data/lib/hanami/model/adapters/sql/query.rb +0 -788
- data/lib/hanami/model/adapters/sql_adapter.rb +0 -296
- data/lib/hanami/model/coercer.rb +0 -74
- data/lib/hanami/model/config/adapter.rb +0 -116
- data/lib/hanami/model/config/mapper.rb +0 -45
- data/lib/hanami/model/mapper.rb +0 -124
- data/lib/hanami/model/mapping/attribute.rb +0 -85
- data/lib/hanami/model/mapping/coercers.rb +0 -314
- data/lib/hanami/model/mapping/collection.rb +0 -490
- data/lib/hanami/model/mapping/collection_coercer.rb +0 -79
@@ -1,6 +1,6 @@
|
|
1
1
|
module Hanami
|
2
2
|
module Model
|
3
|
-
|
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
|
-
|
11
|
+
# @since 0.7.0
|
12
|
+
# @api private
|
13
|
+
attr_reader :raw
|
12
14
|
|
13
|
-
|
14
|
-
|
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
|
-
#
|
55
|
+
# # => 'postgres'
|
52
56
|
#
|
53
57
|
# @since 0.5.0
|
54
58
|
# @api private
|
55
59
|
def database_type
|
56
|
-
|
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
|
-
|
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
|
-
|
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
|
-
!
|
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(
|
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
|
-
|
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
|
-
|
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 #{
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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 #{
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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=#{
|
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=#{
|
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=#{
|
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
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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 -
|
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 #{
|
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 #{
|
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 |
|
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
|
-
|
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: #{
|
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: #{
|
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(
|
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 #{
|
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 #{
|
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 #{
|
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
|