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
@@ -0,0 +1,118 @@
|
|
1
|
+
module Hanami
|
2
|
+
module Model
|
3
|
+
module Plugins
|
4
|
+
# Automatically set/update timestamp columns for create/update commands
|
5
|
+
#
|
6
|
+
# @since 0.7.0
|
7
|
+
# @api private
|
8
|
+
module Timestamps
|
9
|
+
# Takes the input and applies the timestamp transformation.
|
10
|
+
# This is an "abstract class", please look at the subclasses for
|
11
|
+
# specific behaviors.
|
12
|
+
#
|
13
|
+
# @since 0.7.0
|
14
|
+
# @api private
|
15
|
+
class InputWithTimestamp < WrappingInput
|
16
|
+
# Conventional timestamp names
|
17
|
+
#
|
18
|
+
# @since 0.7.0
|
19
|
+
# @api private
|
20
|
+
TIMESTAMPS = [:created_at, :updated_at].freeze
|
21
|
+
|
22
|
+
# @since 0.7.0
|
23
|
+
# @api private
|
24
|
+
def initialize(relation, input)
|
25
|
+
super
|
26
|
+
columns = relation.columns.sort
|
27
|
+
@timestamps = (columns & TIMESTAMPS) == TIMESTAMPS
|
28
|
+
end
|
29
|
+
|
30
|
+
# Processes the input
|
31
|
+
#
|
32
|
+
# @since 0.7.0
|
33
|
+
# @api private
|
34
|
+
def [](value)
|
35
|
+
return value unless timestamps?
|
36
|
+
_touch(@input[value], Time.now)
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
# @since 0.7.0
|
42
|
+
# @api private
|
43
|
+
def _touch(_value)
|
44
|
+
raise NotImplementedError
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# @since 0.7.0
|
50
|
+
# @api private
|
51
|
+
def timestamps?
|
52
|
+
@timestamps
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Updates <tt>updated_at</tt> timestamp for update commands
|
57
|
+
#
|
58
|
+
# @since 0.7.0
|
59
|
+
# @api private
|
60
|
+
class InputWithUpdateTimestamp < InputWithTimestamp
|
61
|
+
protected
|
62
|
+
|
63
|
+
# @since 0.7.0
|
64
|
+
# @api private
|
65
|
+
def _touch(value, now)
|
66
|
+
value[:updated_at] = now
|
67
|
+
value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Sets <tt>created_at</tt> and <tt>updated_at</tt> timestamps for create commands
|
72
|
+
#
|
73
|
+
# @since 0.7.0
|
74
|
+
# @api private
|
75
|
+
class InputWithCreateTimestamp < InputWithUpdateTimestamp
|
76
|
+
protected
|
77
|
+
|
78
|
+
# @since 0.7.0
|
79
|
+
# @api private
|
80
|
+
def _touch(value, now)
|
81
|
+
super
|
82
|
+
value[:created_at] = now
|
83
|
+
value
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Class interface
|
88
|
+
#
|
89
|
+
# @since 0.7.0
|
90
|
+
# @api private
|
91
|
+
module ClassMethods
|
92
|
+
# Build an input processor according to the current command (create or update).
|
93
|
+
#
|
94
|
+
# @since 0.7.0
|
95
|
+
# @api private
|
96
|
+
def build(relation, options = {})
|
97
|
+
plugin = if self < ROM::Commands::Create
|
98
|
+
InputWithCreateTimestamp
|
99
|
+
else
|
100
|
+
InputWithUpdateTimestamp
|
101
|
+
end
|
102
|
+
|
103
|
+
input(plugin.new(relation, input))
|
104
|
+
super(relation, options.merge(input: input))
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# @since 0.7.0
|
109
|
+
# @api private
|
110
|
+
def self.included(klass)
|
111
|
+
super
|
112
|
+
|
113
|
+
klass.extend ClassMethods
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative 'entity_name'
|
2
|
+
require 'hanami/utils/string'
|
3
|
+
|
4
|
+
module Hanami
|
5
|
+
module Model
|
6
|
+
# Conventional name for relations.
|
7
|
+
#
|
8
|
+
# Given a repository named <tt>SourceFileRepository</tt>, the associated
|
9
|
+
# relation will be <tt>:source_files</tt>.
|
10
|
+
#
|
11
|
+
# @since 0.7.0
|
12
|
+
# @api private
|
13
|
+
class RelationName < EntityName
|
14
|
+
# @param name [Class,String] the class or its name
|
15
|
+
# @return [Symbol] the relation name
|
16
|
+
#
|
17
|
+
# @since 0.7.0
|
18
|
+
# @api private
|
19
|
+
def self.new(name)
|
20
|
+
Utils::String.new(super).underscore.pluralize.to_sym
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'rom-sql'
|
2
|
+
require 'hanami/utils'
|
3
|
+
|
4
|
+
module Hanami
|
5
|
+
# Hanami::Model migrations
|
6
|
+
module Model
|
7
|
+
require 'hanami/model/error'
|
8
|
+
require 'hanami/model/association'
|
9
|
+
require 'hanami/model/migration'
|
10
|
+
|
11
|
+
# Define a migration
|
12
|
+
#
|
13
|
+
# It must define an up/down strategy to write schema changes (up) and to
|
14
|
+
# rollback them (down).
|
15
|
+
#
|
16
|
+
# We can use <tt>up</tt> and <tt>down</tt> blocks for custom strategies, or
|
17
|
+
# only one <tt>change</tt> block that automatically implements "down" strategy.
|
18
|
+
#
|
19
|
+
# @param blk [Proc] a block that defines up/down or change database migration
|
20
|
+
#
|
21
|
+
# @since 0.4.0
|
22
|
+
#
|
23
|
+
# @example Use up/down blocks
|
24
|
+
# Hanami::Model.migration do
|
25
|
+
# up do
|
26
|
+
# create_table :books do
|
27
|
+
# primary_key :id
|
28
|
+
# column :book, String
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# down do
|
33
|
+
# drop_table :books
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# @example Use change block
|
38
|
+
# Hanami::Model.migration do
|
39
|
+
# change do
|
40
|
+
# create_table :books do
|
41
|
+
# primary_key :id
|
42
|
+
# column :book, String
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# # DOWN strategy is automatically generated
|
47
|
+
# end
|
48
|
+
def self.migration(&blk)
|
49
|
+
Migration.new(configuration.gateways[:default], &blk)
|
50
|
+
end
|
51
|
+
|
52
|
+
# SQL adapter
|
53
|
+
#
|
54
|
+
# @since 0.7.0
|
55
|
+
module Sql
|
56
|
+
require 'hanami/model/sql/types'
|
57
|
+
require 'hanami/model/sql/entity/schema'
|
58
|
+
|
59
|
+
# Returns a SQL fragment that references a database function by the given name
|
60
|
+
# This is useful for database migrations
|
61
|
+
#
|
62
|
+
# @param name [String,Symbol] the function name
|
63
|
+
# @return [String] the SQL fragment
|
64
|
+
#
|
65
|
+
# @since 0.7.0
|
66
|
+
#
|
67
|
+
# @example
|
68
|
+
# Hanami::Model.migration do
|
69
|
+
# up do
|
70
|
+
# execute 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'
|
71
|
+
#
|
72
|
+
# create_table :source_files do
|
73
|
+
# column :id, 'uuid', primary_key: true, default: Hanami::Model::Sql.function(:uuid_generate_v4)
|
74
|
+
# # ...
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# down do
|
79
|
+
# drop_table :source_files
|
80
|
+
# execute 'DROP EXTENSION "uuid-ossp"'
|
81
|
+
# end
|
82
|
+
# end
|
83
|
+
def self.function(name)
|
84
|
+
Sequel.function(name)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns a literal SQL fragment for the given SQL fragment.
|
88
|
+
# This is useful for database migrations
|
89
|
+
#
|
90
|
+
# @param string [String] the SQL fragment
|
91
|
+
# @return [String] the literal SQL fragment
|
92
|
+
#
|
93
|
+
# @since 0.7.0
|
94
|
+
#
|
95
|
+
# @example
|
96
|
+
# Hanami::Model.migration do
|
97
|
+
# up do
|
98
|
+
# execute %{
|
99
|
+
# CREATE TYPE inventory_item AS (
|
100
|
+
# name text,
|
101
|
+
# supplier_id integer,
|
102
|
+
# price numeric
|
103
|
+
# );
|
104
|
+
# }
|
105
|
+
#
|
106
|
+
# create_table :items do
|
107
|
+
# column :item, 'inventory_item', default: Hanami::Model::Sql.literal("ROW('fuzzy dice', 42, 1.99)")
|
108
|
+
# # ...
|
109
|
+
# end
|
110
|
+
# end
|
111
|
+
#
|
112
|
+
# down do
|
113
|
+
# drop_table :itmes
|
114
|
+
# execute 'DROP TYPE inventory_item'
|
115
|
+
# end
|
116
|
+
# end
|
117
|
+
def self.literal(string)
|
118
|
+
Sequel.lit(string)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Returns SQL fragment for ascending order for the given column
|
122
|
+
#
|
123
|
+
# @param column [Symbol] the column name
|
124
|
+
# @return [String] the SQL fragment
|
125
|
+
#
|
126
|
+
# @since 0.7.0
|
127
|
+
def self.asc(column)
|
128
|
+
Sequel.asc(column)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Returns SQL fragment for descending order for the given column
|
132
|
+
#
|
133
|
+
# @param column [Symbol] the column name
|
134
|
+
# @return [String] the SQL fragment
|
135
|
+
#
|
136
|
+
# @since 0.7.0
|
137
|
+
def self.desc(column)
|
138
|
+
Sequel.desc(column)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
Error.register(ROM::SQL::DatabaseError, DatabaseError)
|
143
|
+
Error.register(ROM::SQL::ConstraintError, ConstraintViolationError)
|
144
|
+
Error.register(ROM::SQL::NotNullConstraintError, NotNullConstraintViolationError)
|
145
|
+
Error.register(ROM::SQL::UniqueConstraintError, UniqueConstraintViolationError)
|
146
|
+
Error.register(ROM::SQL::CheckConstraintError, CheckConstraintViolationError)
|
147
|
+
Error.register(ROM::SQL::ForeignKeyConstraintError, ForeignKeyConstraintViolationError)
|
148
|
+
|
149
|
+
Error.register(Java::JavaSql::SQLException, DatabaseError) if Utils.jruby?
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
Sequel.default_timezone = :utc
|
154
|
+
|
155
|
+
ROM.plugins do
|
156
|
+
adapter :sql do
|
157
|
+
register :mapping, Hanami::Model::Plugins::Mapping, type: :command
|
158
|
+
register :schema, Hanami::Model::Plugins::Schema, type: :command
|
159
|
+
register :timestamps, Hanami::Model::Plugins::Timestamps, type: :command
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module Model
|
5
|
+
module Sql
|
6
|
+
# SQL console
|
7
|
+
#
|
8
|
+
# @since 0.7.0
|
9
|
+
# @api private
|
10
|
+
class Console
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
def_delegator :console, :connection_string
|
14
|
+
|
15
|
+
# @since 0.7.0
|
16
|
+
# @api private
|
17
|
+
def initialize(uri)
|
18
|
+
@uri = URI.parse(uri)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# @since 0.7.0
|
24
|
+
# @api private
|
25
|
+
def console # rubocop:disable Metrics/MethodLength
|
26
|
+
case @uri.scheme
|
27
|
+
when 'sqlite'
|
28
|
+
require 'hanami/model/sql/consoles/sqlite'
|
29
|
+
Sql::Consoles::Sqlite.new(@uri)
|
30
|
+
when 'postgres'
|
31
|
+
require 'hanami/model/sql/consoles/postgresql'
|
32
|
+
Sql::Consoles::Postgresql.new(@uri)
|
33
|
+
when 'mysql', 'mysql2'
|
34
|
+
require 'hanami/model/sql/consoles/mysql'
|
35
|
+
Sql::Consoles::Mysql.new(@uri)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Hanami
|
2
|
+
module Model
|
3
|
+
module Sql
|
4
|
+
module Consoles
|
5
|
+
# Abstract adapter
|
6
|
+
#
|
7
|
+
# @since 0.7.0
|
8
|
+
# @api private
|
9
|
+
class Abstract
|
10
|
+
# @since 0.7.0
|
11
|
+
# @api private
|
12
|
+
def initialize(uri)
|
13
|
+
@uri = uri
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# @since 0.7.0
|
19
|
+
# @api private
|
20
|
+
def database_name
|
21
|
+
@uri.path.sub(/^\//, '')
|
22
|
+
end
|
23
|
+
|
24
|
+
# @since 0.7.0
|
25
|
+
# @api private
|
26
|
+
def concat(*tokens)
|
27
|
+
tokens.join
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require_relative 'abstract'
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module Model
|
5
|
+
module Sql
|
6
|
+
module Consoles
|
7
|
+
# MySQL adapter
|
8
|
+
#
|
9
|
+
# @since 0.7.0
|
10
|
+
# @api private
|
11
|
+
class Mysql < Abstract
|
12
|
+
# @since 0.7.0
|
13
|
+
# @api private
|
14
|
+
COMMAND = 'mysql'.freeze
|
15
|
+
|
16
|
+
# @since 0.7.0
|
17
|
+
# @api private
|
18
|
+
def connection_string
|
19
|
+
concat(command, host, database, port, username, password)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# @since 0.7.0
|
25
|
+
# @api private
|
26
|
+
def command
|
27
|
+
COMMAND
|
28
|
+
end
|
29
|
+
|
30
|
+
# @since 0.7.0
|
31
|
+
# @api private
|
32
|
+
def host
|
33
|
+
" -h #{@uri.host}"
|
34
|
+
end
|
35
|
+
|
36
|
+
# @since 0.7.0
|
37
|
+
# @api private
|
38
|
+
def database
|
39
|
+
" -D #{database_name}"
|
40
|
+
end
|
41
|
+
|
42
|
+
# @since 0.7.0
|
43
|
+
# @api private
|
44
|
+
def port
|
45
|
+
" -P #{@uri.port}" unless @uri.port.nil?
|
46
|
+
end
|
47
|
+
|
48
|
+
# @since 0.7.0
|
49
|
+
# @api private
|
50
|
+
def username
|
51
|
+
" -u #{@uri.user}" unless @uri.user.nil?
|
52
|
+
end
|
53
|
+
|
54
|
+
# @since 0.7.0
|
55
|
+
# @api private
|
56
|
+
def password
|
57
|
+
" -p #{@uri.password}" unless @uri.password.nil?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|