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
data/lib/hanami/model.rb
CHANGED
@@ -1,172 +1,86 @@
|
|
1
|
-
require '
|
1
|
+
require 'rom'
|
2
|
+
require 'concurrent'
|
2
3
|
require 'hanami/entity'
|
3
|
-
require 'hanami/entity/dirty_tracking'
|
4
4
|
require 'hanami/repository'
|
5
|
-
require 'hanami/model/mapper'
|
6
|
-
require 'hanami/model/configuration'
|
7
|
-
require 'hanami/model/error'
|
8
5
|
|
9
6
|
module Hanami
|
10
|
-
#
|
7
|
+
# Hanami persistence
|
11
8
|
#
|
12
9
|
# @since 0.1.0
|
13
10
|
module Model
|
14
|
-
|
11
|
+
require 'hanami/model/version'
|
12
|
+
require 'hanami/model/error'
|
13
|
+
require 'hanami/model/configuration'
|
14
|
+
require 'hanami/model/configurator'
|
15
|
+
require 'hanami/model/mapping'
|
16
|
+
require 'hanami/model/plugins'
|
15
17
|
|
16
|
-
# Framework configuration
|
17
|
-
#
|
18
|
-
# @since 0.2.0
|
19
18
|
# @api private
|
20
|
-
|
21
|
-
|
19
|
+
# @since 0.7.0
|
20
|
+
@__repositories__ = Concurrent::Array.new # rubocop:disable Style/VariableNumber
|
22
21
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
22
|
+
class << self
|
23
|
+
# @since 0.7.0
|
24
|
+
# @api private
|
25
|
+
attr_reader :config
|
26
|
+
|
27
|
+
# @since 0.7.0
|
28
|
+
# @api private
|
29
|
+
attr_reader :loaded
|
30
|
+
|
31
|
+
# @since 0.7.0
|
32
|
+
# @api private
|
33
|
+
alias loaded? loaded
|
34
|
+
end
|
35
|
+
|
36
|
+
# Configure the framework
|
29
37
|
#
|
30
|
-
# @
|
38
|
+
# @since 0.1.0
|
31
39
|
#
|
32
40
|
# @example
|
33
41
|
# require 'hanami/model'
|
34
42
|
#
|
35
43
|
# Hanami::Model.configure do
|
36
|
-
# adapter
|
37
|
-
#
|
38
|
-
# mapping do
|
39
|
-
# collection :users do
|
40
|
-
# entity User
|
44
|
+
# adapter :sql, ENV['DATABASE_URL']
|
41
45
|
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
# end
|
45
|
-
# end
|
46
|
+
# migrations 'db/migrations'
|
47
|
+
# schema 'db/schema.sql'
|
46
48
|
# end
|
47
|
-
|
48
|
-
|
49
|
-
# The above example has name :sql, thus derived class will be `Hanami::Model::Adapters::SqlAdapter`
|
50
|
-
def self.configure(&blk)
|
51
|
-
configuration.instance_eval(&blk)
|
49
|
+
def self.configure(&block)
|
50
|
+
@config = Configurator.build(&block)
|
52
51
|
self
|
53
52
|
end
|
54
53
|
|
55
|
-
#
|
54
|
+
# Current configuration
|
56
55
|
#
|
57
|
-
# @since 0.
|
58
|
-
|
59
|
-
|
60
|
-
configuration.load!
|
56
|
+
# @since 0.1.0
|
57
|
+
def self.configuration
|
58
|
+
@configuration ||= Configuration.new(config)
|
61
59
|
end
|
62
60
|
|
63
|
-
#
|
64
|
-
#
|
65
|
-
# @since 0.2.0
|
61
|
+
# @since 0.7.0
|
66
62
|
# @api private
|
67
|
-
def self.
|
68
|
-
|
63
|
+
def self.repositories
|
64
|
+
@__repositories__
|
69
65
|
end
|
70
66
|
|
71
|
-
#
|
72
|
-
# of the framework.
|
73
|
-
#
|
74
|
-
# The new instance of the framework will be completely decoupled from the
|
75
|
-
# original. It will inherit the configuration, but all the changes that
|
76
|
-
# happen after the duplication, won't be reflected on the other copies.
|
77
|
-
#
|
78
|
-
# @return [Module] a copy of Hanami::Model
|
79
|
-
#
|
80
|
-
# @since 0.2.0
|
67
|
+
# @since 0.7.0
|
81
68
|
# @api private
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
#
|
86
|
-
# module MyApp
|
87
|
-
# Model = Hanami::Model.dupe
|
88
|
-
# end
|
89
|
-
#
|
90
|
-
# MyApp::Model == Hanami::Model # => false
|
91
|
-
#
|
92
|
-
# MyApp::Model.configuration ==
|
93
|
-
# Hanami::Model.configuration # => false
|
94
|
-
#
|
95
|
-
# @example Inheriting configuration
|
96
|
-
# require 'hanami/model'
|
97
|
-
#
|
98
|
-
# Hanami::Model.configure do
|
99
|
-
# adapter type: :sql, uri: 'sqlite3://uri'
|
100
|
-
# end
|
101
|
-
#
|
102
|
-
# module MyApp
|
103
|
-
# Model = Hanami::Model.dupe
|
104
|
-
# end
|
105
|
-
#
|
106
|
-
# module MyApi
|
107
|
-
# Model = Hanami::Model.dupe
|
108
|
-
# Model.configure do
|
109
|
-
# adapter type: :sql, uri: 'postgresql://uri'
|
110
|
-
# end
|
111
|
-
# end
|
112
|
-
#
|
113
|
-
# Hanami::Model.configuration.adapter_config.uri # => 'sqlite3://uri'
|
114
|
-
# MyApp::Model.configuration.adapter_config.uri # => 'sqlite3://uri'
|
115
|
-
# MyApi::Model.configuration.adapter_config.uri # => 'postgresql://uri'
|
116
|
-
def self.dupe
|
117
|
-
dup.tap do |duplicated|
|
118
|
-
duplicated.configuration = Configuration.new
|
119
|
-
end
|
69
|
+
def self.container
|
70
|
+
raise 'Not loaded' unless loaded?
|
71
|
+
@container
|
120
72
|
end
|
121
73
|
|
122
|
-
#
|
123
|
-
#
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
#
|
131
|
-
# @see Hanami::Model#dupe
|
132
|
-
# @see Hanami::Model::Configuration
|
133
|
-
#
|
134
|
-
# @example Basic usage
|
135
|
-
# require 'hanami/model'
|
136
|
-
#
|
137
|
-
# module MyApp
|
138
|
-
# Model = Hanami::Model.dupe
|
139
|
-
# end
|
140
|
-
#
|
141
|
-
# # It will:
|
142
|
-
# #
|
143
|
-
# # 1. Generate MyApp::Model
|
144
|
-
# # 2. Generate MyApp::Entity
|
145
|
-
# # 3. Generate MyApp::Repository
|
146
|
-
#
|
147
|
-
# MyApp::Model == Hanami::Model # => false
|
148
|
-
# MyApp::Repository == Hanami::Repository # => false
|
149
|
-
#
|
150
|
-
# @example Block usage
|
151
|
-
# require 'hanami/model'
|
152
|
-
#
|
153
|
-
# module MyApp
|
154
|
-
# Model = Hanami::Model.duplicate(self) do
|
155
|
-
# adapter type: :memory, uri: 'memory://localhost'
|
156
|
-
# end
|
157
|
-
# end
|
158
|
-
#
|
159
|
-
# Hanami::Model.configuration.adapter_config # => nil
|
160
|
-
# MyApp::Model.configuration.adapter_config # => #<Hanami::Model::Config::Adapter:0x007ff0ff0244f8 @type=:memory, @uri="memory://localhost", @class_name="MemoryAdapter">
|
161
|
-
def self.duplicate(mod, &blk)
|
162
|
-
dupe.tap do |duplicated|
|
163
|
-
mod.module_eval %{
|
164
|
-
Entity = Hanami::Entity.dup
|
165
|
-
Repository = Hanami::Repository.dup
|
166
|
-
}
|
74
|
+
# @since 0.1.0
|
75
|
+
def self.load!(&blk) # rubocop:disable Metrics/AbcSize
|
76
|
+
configuration.setup.auto_registration(config.directory.to_s) unless config.directory.nil?
|
77
|
+
configuration.instance_eval(&blk) if block_given?
|
78
|
+
repositories.each(&:load!)
|
79
|
+
|
80
|
+
@container = ROM.container(configuration)
|
81
|
+
configuration.define_entities_mappings(@container, repositories)
|
167
82
|
|
168
|
-
|
169
|
-
end
|
83
|
+
@loaded = true
|
170
84
|
end
|
171
85
|
end
|
172
86
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rom-sql'
|
2
|
+
require 'hanami/model/associations/has_many'
|
3
|
+
require 'hanami/model/associations/belongs_to'
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
module Model
|
7
|
+
# Association factory
|
8
|
+
#
|
9
|
+
# @since 0.7.0
|
10
|
+
# @api private
|
11
|
+
class Association
|
12
|
+
# Instantiate an association
|
13
|
+
#
|
14
|
+
# @since 0.7.0
|
15
|
+
# @api private
|
16
|
+
def self.build(repository, target, subject)
|
17
|
+
lookup(repository.root.associations[target])
|
18
|
+
.new(repository, repository.root.name.to_sym, target, subject)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Translate ROM SQL associations into Hanami::Model associations
|
22
|
+
#
|
23
|
+
# @since 0.7.0
|
24
|
+
# @api private
|
25
|
+
def self.lookup(association)
|
26
|
+
case association
|
27
|
+
when ROM::SQL::Association::OneToMany
|
28
|
+
Associations::HasMany
|
29
|
+
when ROM::SQL::Association::ManyToOne
|
30
|
+
Associations::BelongsTo
|
31
|
+
else
|
32
|
+
raise "Unsupported association: #{association}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'hanami/model/types'
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module Model
|
5
|
+
module Associations
|
6
|
+
# Many-To-One association
|
7
|
+
#
|
8
|
+
# @since 0.7.0
|
9
|
+
# @api private
|
10
|
+
class BelongsTo
|
11
|
+
# @since 0.7.0
|
12
|
+
# @api private
|
13
|
+
def self.schema_type(entity)
|
14
|
+
Sql::Types::Schema::AssociationType.new(entity)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Hanami
|
2
|
+
module Model
|
3
|
+
module Associations
|
4
|
+
# Auto-infer relations linked to repository's associations
|
5
|
+
#
|
6
|
+
# @since 0.7.0
|
7
|
+
# @api private
|
8
|
+
class Dsl
|
9
|
+
# @since 0.7.0
|
10
|
+
# @api private
|
11
|
+
def initialize(repository, &blk)
|
12
|
+
@repository = repository
|
13
|
+
instance_eval(&blk)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @since 0.7.0
|
17
|
+
# @api private
|
18
|
+
def has_many(relation, *)
|
19
|
+
@repository.__send__(:relations, relation)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @since 0.7.0
|
23
|
+
# @api private
|
24
|
+
def belongs_to(*)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
require 'hanami/model/types'
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module Model
|
5
|
+
module Associations
|
6
|
+
# One-To-Many association
|
7
|
+
#
|
8
|
+
# @since 0.7.0
|
9
|
+
# @api private
|
10
|
+
class HasMany
|
11
|
+
# @since 0.7.0
|
12
|
+
# @api private
|
13
|
+
def self.schema_type(entity)
|
14
|
+
type = Sql::Types::Schema::AssociationType.new(entity)
|
15
|
+
Types::Strict::Array.member(type)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @since 0.7.0
|
19
|
+
# @api private
|
20
|
+
attr_reader :repository
|
21
|
+
|
22
|
+
# @since 0.7.0
|
23
|
+
# @api private
|
24
|
+
attr_reader :source
|
25
|
+
|
26
|
+
# @since 0.7.0
|
27
|
+
# @api private
|
28
|
+
attr_reader :target
|
29
|
+
|
30
|
+
# @since 0.7.0
|
31
|
+
# @api private
|
32
|
+
attr_reader :subject
|
33
|
+
|
34
|
+
# @since 0.7.0
|
35
|
+
# @api private
|
36
|
+
attr_reader :scope
|
37
|
+
|
38
|
+
# @since 0.7.0
|
39
|
+
# @api private
|
40
|
+
def initialize(repository, source, target, subject, scope = nil)
|
41
|
+
@repository = repository
|
42
|
+
@source = source
|
43
|
+
@target = target
|
44
|
+
@subject = subject.to_hash unless subject.nil?
|
45
|
+
@scope = scope || _build_scope
|
46
|
+
freeze
|
47
|
+
end
|
48
|
+
|
49
|
+
# @since 0.7.0
|
50
|
+
# @api private
|
51
|
+
def create(data)
|
52
|
+
entity.new(
|
53
|
+
command(:create, aggregate(target), use: [:timestamps])
|
54
|
+
.call(data)
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @since 0.7.0
|
59
|
+
# @api private
|
60
|
+
def add(data)
|
61
|
+
command(:create, relation(target), use: [:timestamps])
|
62
|
+
.call(associate(data))
|
63
|
+
end
|
64
|
+
|
65
|
+
# @since 0.7.0
|
66
|
+
# @api private
|
67
|
+
def remove(id)
|
68
|
+
target_relation = relation(target)
|
69
|
+
|
70
|
+
command(:update, target_relation.where(target_relation.primary_key => id), use: [:timestamps])
|
71
|
+
.call(unassociate)
|
72
|
+
end
|
73
|
+
|
74
|
+
# @since 0.7.0
|
75
|
+
# @api private
|
76
|
+
def delete
|
77
|
+
scope.delete
|
78
|
+
end
|
79
|
+
|
80
|
+
# @since 0.7.0
|
81
|
+
# @api private
|
82
|
+
def each(&blk)
|
83
|
+
scope.each(&blk)
|
84
|
+
end
|
85
|
+
|
86
|
+
# @since 0.7.0
|
87
|
+
# @api private
|
88
|
+
def map(&blk)
|
89
|
+
to_a.map(&blk)
|
90
|
+
end
|
91
|
+
|
92
|
+
# @since 0.7.0
|
93
|
+
# @api private
|
94
|
+
def to_a
|
95
|
+
scope.to_a
|
96
|
+
end
|
97
|
+
|
98
|
+
# @since 0.7.0
|
99
|
+
# @api private
|
100
|
+
def where(condition)
|
101
|
+
__new__(scope.where(condition))
|
102
|
+
end
|
103
|
+
|
104
|
+
# @since 0.7.0
|
105
|
+
# @api private
|
106
|
+
def count
|
107
|
+
scope.count
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
# @since 0.7.0
|
113
|
+
# @api private
|
114
|
+
def command(target, relation, options = {})
|
115
|
+
repository.command(target, relation, options)
|
116
|
+
end
|
117
|
+
|
118
|
+
# @since 0.7.0
|
119
|
+
# @api private
|
120
|
+
def entity
|
121
|
+
repository.class.entity
|
122
|
+
end
|
123
|
+
|
124
|
+
# @since 0.7.0
|
125
|
+
# @api private
|
126
|
+
def relation(name)
|
127
|
+
repository.relations[name]
|
128
|
+
end
|
129
|
+
|
130
|
+
# @since 0.7.0
|
131
|
+
# @api private
|
132
|
+
def aggregate(name)
|
133
|
+
repository.aggregate(name)
|
134
|
+
end
|
135
|
+
|
136
|
+
# @since 0.7.0
|
137
|
+
# @api private
|
138
|
+
def association(name)
|
139
|
+
relation(target).associations[name]
|
140
|
+
end
|
141
|
+
|
142
|
+
# @since 0.7.0
|
143
|
+
# @api private
|
144
|
+
def associate(data)
|
145
|
+
relation(source)
|
146
|
+
.associations[target]
|
147
|
+
.associate(container.relations, data, subject)
|
148
|
+
end
|
149
|
+
|
150
|
+
# @since 0.7.0
|
151
|
+
# @api private
|
152
|
+
def unassociate
|
153
|
+
{ foreign_key => nil }
|
154
|
+
end
|
155
|
+
|
156
|
+
# @since 0.7.0
|
157
|
+
# @api private
|
158
|
+
def container
|
159
|
+
repository.container
|
160
|
+
end
|
161
|
+
|
162
|
+
# @since 0.7.0
|
163
|
+
# @api private
|
164
|
+
def primary_key
|
165
|
+
association_keys.first
|
166
|
+
end
|
167
|
+
|
168
|
+
# @since 0.7.0
|
169
|
+
# @api private
|
170
|
+
def foreign_key
|
171
|
+
association_keys.last
|
172
|
+
end
|
173
|
+
|
174
|
+
# Returns primary key and foreign key
|
175
|
+
#
|
176
|
+
# @since 0.7.0
|
177
|
+
# @api private
|
178
|
+
def association_keys
|
179
|
+
relation(source)
|
180
|
+
.associations[target]
|
181
|
+
.__send__(:join_key_map, container.relations)
|
182
|
+
end
|
183
|
+
|
184
|
+
# @since 0.7.0
|
185
|
+
# @api private
|
186
|
+
def _build_scope
|
187
|
+
result = relation(target)
|
188
|
+
result = result.where(foreign_key => subject.fetch(primary_key)) unless subject.nil?
|
189
|
+
result.as(Repository::MAPPER_NAME)
|
190
|
+
end
|
191
|
+
|
192
|
+
# @since 0.7.0
|
193
|
+
# @api private
|
194
|
+
def __new__(new_scope)
|
195
|
+
self.class.new(repository, source, target, subject, new_scope)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|