hanami-model 1.0.4 → 1.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -7
- data/README.md +1 -1
- data/hanami-model.gemspec +3 -4
- data/lib/hanami/entity.rb +4 -3
- data/lib/hanami/entity/schema.rb +20 -6
- data/lib/hanami/model.rb +1 -1
- data/lib/hanami/model/association.rb +9 -1
- data/lib/hanami/model/associations/belongs_to.rb +83 -2
- data/lib/hanami/model/associations/dsl.rb +9 -3
- data/lib/hanami/model/associations/has_many.rb +6 -4
- data/lib/hanami/model/associations/has_one.rb +157 -0
- data/lib/hanami/model/associations/many_to_many.rb +182 -0
- data/lib/hanami/model/entity_name.rb +1 -1
- data/lib/hanami/model/error.rb +4 -4
- data/lib/hanami/model/mapped_relation.rb +1 -1
- data/lib/hanami/model/migrator.rb +2 -2
- data/lib/hanami/model/migrator/adapter.rb +2 -2
- data/lib/hanami/model/migrator/postgres_adapter.rb +21 -20
- data/lib/hanami/model/plugins/mapping.rb +1 -1
- data/lib/hanami/model/plugins/timestamps.rb +1 -1
- data/lib/hanami/model/relation_name.rb +1 -1
- data/lib/hanami/model/sql.rb +1 -1
- data/lib/hanami/model/sql/entity/schema.rb +3 -3
- data/lib/hanami/model/sql/types.rb +3 -13
- data/lib/hanami/model/types.rb +2 -1
- data/lib/hanami/model/version.rb +1 -1
- data/lib/hanami/repository.rb +4 -4
- metadata +13 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8c17cc417f16015bc4dafe69467c74e94198a10a
|
4
|
+
data.tar.gz: f18523f952dce748ae2eeba9c6cd4c206236d1cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d7ef2340e7ccd94518079de9aef5ab122fcda34c6daaa7f557bd3f982c4c7b62b36614433af0c4c43935e09677a5d075ab1d7bf005ae656e3e2221c0851235e8
|
7
|
+
data.tar.gz: 511fdf9ee9753b367cc8f03e6f13e8e7058f15f64fbb0ab9e1728d4dfc831b1a4cca0916872159ec33e5bc0dccf408da12463b6bb77daeb5976ab4013cca6a6a
|
data/CHANGELOG.md
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
# Hanami::Model
|
2
2
|
A persistence layer for Hanami
|
3
3
|
|
4
|
-
## v1.0.
|
5
|
-
###
|
6
|
-
- [
|
7
|
-
- [
|
8
|
-
- [
|
4
|
+
## v1.1.0.beta1 - 2017-08-11
|
5
|
+
### Added
|
6
|
+
- [Marcello Rocha] One-To-Many association (aka `belongs_to`)
|
7
|
+
- [Marcello Rocha] One-To-One association (aka `has_one`)
|
8
|
+
- [Marcello Rocha] Many-To-Many association (aka `has_many :through`)
|
9
|
+
- [Luca Guidi] Introduced new extra behaviors for entity manual schema: `:schema` (default), `:strict`, `:weak`, and `:permissive`
|
9
10
|
|
10
|
-
## v1.0.3 - 2017-10-11
|
11
11
|
### Fixed
|
12
|
-
- [
|
12
|
+
- [Sean Collins] Enhanced error message for Postgres `db create` and `db drop` when `createdb` and `dropdb` aren't in `PATH`
|
13
13
|
|
14
14
|
## v1.0.2 - 2017-08-04
|
15
15
|
### Fixed
|
data/README.md
CHANGED
@@ -16,7 +16,7 @@ Like all the other Hanami components, it can be used as a standalone framework o
|
|
16
16
|
|
17
17
|
[![Gem Version](https://badge.fury.io/rb/hanami-model.svg)](http://badge.fury.io/rb/hanami-model)
|
18
18
|
[![Build Status](https://secure.travis-ci.org/hanami/model.svg?branch=master)](http://travis-ci.org/hanami/model?branch=master)
|
19
|
-
[![Coverage](https://
|
19
|
+
[![Coverage](https://codecov.io/gh/hanami/model/branch/master/graph/badge.svg)](https://codecov.io/gh/hanami/model)
|
20
20
|
[![Code Climate](https://codeclimate.com/github/hanami/model/badges/gpa.svg)](https://codeclimate.com/github/hanami/model)
|
21
21
|
[![Dependencies](https://gemnasium.com/hanami/model.svg)](https://gemnasium.com/hanami/model)
|
22
22
|
[![Inline docs](http://inch-ci.org/github/hanami/model.png)](http://inch-ci.org/github/hanami/model)
|
data/hanami-model.gemspec
CHANGED
@@ -20,11 +20,10 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.require_paths = ['lib']
|
21
21
|
spec.required_ruby_version = '>= 2.3.0'
|
22
22
|
|
23
|
-
spec.add_runtime_dependency 'hanami-utils', '
|
24
|
-
spec.add_runtime_dependency 'rom',
|
25
|
-
spec.add_runtime_dependency 'rom-sql', '~> 1.3', '>= 1.3.5'
|
23
|
+
spec.add_runtime_dependency 'hanami-utils', '1.1.0.beta1'
|
24
|
+
spec.add_runtime_dependency 'rom-sql', '~> 1.3'
|
26
25
|
spec.add_runtime_dependency 'rom-repository', '~> 1.4'
|
27
|
-
spec.add_runtime_dependency 'dry-types', '~> 0.11
|
26
|
+
spec.add_runtime_dependency 'dry-types', '~> 0.11'
|
28
27
|
spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
|
29
28
|
|
30
29
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
data/lib/hanami/entity.rb
CHANGED
@@ -33,7 +33,7 @@ module Hanami
|
|
33
33
|
# end
|
34
34
|
# end
|
35
35
|
#
|
36
|
-
# **Hanami::Model** ships `Hanami::Entity` for developers' convenience.
|
36
|
+
# **Hanami::Model** ships `Hanami::Entity` for developers's convenience.
|
37
37
|
#
|
38
38
|
# **Hanami::Model** depends on a narrow and well-defined interface for an
|
39
39
|
# Entity - `#id`, `#id=`, `#initialize(attributes={})`.If your object
|
@@ -75,13 +75,14 @@ module Hanami
|
|
75
75
|
# attributes via this DSL. If you don't do any setup, the entity accepts all
|
76
76
|
# the given attributes.
|
77
77
|
#
|
78
|
+
# @param type [Symbol] the type of schema to build
|
78
79
|
# @param blk [Proc] the block that defines the attributes
|
79
80
|
#
|
80
81
|
# @since 0.7.0
|
81
82
|
#
|
82
83
|
# @see Hanami::Entity
|
83
|
-
def attributes(&blk)
|
84
|
-
self.schema = Schema.new(&blk)
|
84
|
+
def attributes(type = nil, &blk)
|
85
|
+
self.schema = Schema.new(type, &blk)
|
85
86
|
@attributes = true
|
86
87
|
end
|
87
88
|
|
data/lib/hanami/entity/schema.rb
CHANGED
@@ -93,11 +93,22 @@ module Hanami
|
|
93
93
|
#
|
94
94
|
# @since 0.7.0
|
95
95
|
class Dsl
|
96
|
+
# @since 1.1.0
|
97
|
+
# @api private
|
98
|
+
TYPES = [:schema, :strict, :weak, :permissive, :strict_with_defaults, :symbolized].freeze
|
99
|
+
|
100
|
+
# @since 1.1.0
|
101
|
+
# @api private
|
102
|
+
DEFAULT_TYPE = TYPES.first
|
103
|
+
|
96
104
|
# @since 0.7.0
|
97
105
|
# @api private
|
98
|
-
def self.build(&blk)
|
106
|
+
def self.build(type, &blk)
|
107
|
+
type ||= DEFAULT_TYPE
|
108
|
+
raise Hanami::Model::Error.new("Unknown schema type: `#{type.inspect}'") unless TYPES.include?(type)
|
109
|
+
|
99
110
|
attributes = new(&blk).to_h
|
100
|
-
[attributes, Hanami::Model::Types::Coercible::Hash.
|
111
|
+
[attributes, Hanami::Model::Types::Coercible::Hash.__send__(type, attributes)]
|
101
112
|
end
|
102
113
|
|
103
114
|
# @since 0.7.0
|
@@ -152,9 +163,9 @@ module Hanami
|
|
152
163
|
#
|
153
164
|
# @since 0.7.0
|
154
165
|
# @api private
|
155
|
-
def initialize(&blk)
|
166
|
+
def initialize(type = nil, &blk)
|
156
167
|
raise LocalJumpError unless block_given?
|
157
|
-
@attributes, @schema = Dsl.build(&blk)
|
168
|
+
@attributes, @schema = Dsl.build(type, &blk)
|
158
169
|
@attributes = Hash[@attributes.map { |k, _| [k, true] }]
|
159
170
|
freeze
|
160
171
|
end
|
@@ -164,6 +175,7 @@ module Hanami
|
|
164
175
|
# @param attributes [#to_hash] the attributes hash
|
165
176
|
#
|
166
177
|
# @raise [TypeError] if the process fails
|
178
|
+
# @raise [ArgumentError] if data is missing, or unknown keys are given
|
167
179
|
#
|
168
180
|
# @since 0.7.0
|
169
181
|
# @api private
|
@@ -171,6 +183,8 @@ module Hanami
|
|
171
183
|
schema.call(attributes)
|
172
184
|
rescue Dry::Types::SchemaError => e
|
173
185
|
raise TypeError.new(e.message)
|
186
|
+
rescue Dry::Types::MissingKeyError, Dry::Types::UnknownKeysError => e
|
187
|
+
raise ArgumentError.new(e.message)
|
174
188
|
end
|
175
189
|
|
176
190
|
# Check if the attribute is known
|
@@ -204,9 +218,9 @@ module Hanami
|
|
204
218
|
#
|
205
219
|
# @since 0.7.0
|
206
220
|
# @api private
|
207
|
-
def initialize(&blk)
|
221
|
+
def initialize(type = nil, &blk)
|
208
222
|
@schema = if block_given?
|
209
|
-
Definition.new(&blk)
|
223
|
+
Definition.new(type, &blk)
|
210
224
|
else
|
211
225
|
Schemaless.new
|
212
226
|
end
|
data/lib/hanami/model.rb
CHANGED
@@ -82,7 +82,7 @@ module Hanami
|
|
82
82
|
|
83
83
|
# Disconnect from the database
|
84
84
|
#
|
85
|
-
# This is useful for
|
85
|
+
# This is useful for reboot applications in production and to ensure that
|
86
86
|
# the framework prunes stale connections.
|
87
87
|
#
|
88
88
|
# @since 1.0.0
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'rom-sql'
|
2
|
-
require 'hanami/model/associations/has_many'
|
3
2
|
require 'hanami/model/associations/belongs_to'
|
3
|
+
require 'hanami/model/associations/has_many'
|
4
|
+
require 'hanami/model/associations/has_one'
|
5
|
+
require 'hanami/model/associations/many_to_many'
|
4
6
|
|
5
7
|
module Hanami
|
6
8
|
module Model
|
@@ -22,8 +24,13 @@ module Hanami
|
|
22
24
|
#
|
23
25
|
# @since 0.7.0
|
24
26
|
# @api private
|
27
|
+
# rubocop:disable Metrics/MethodLength
|
25
28
|
def self.lookup(association)
|
26
29
|
case association
|
30
|
+
when ROM::SQL::Association::ManyToMany
|
31
|
+
Associations::ManyToMany
|
32
|
+
when ROM::SQL::Association::OneToOne
|
33
|
+
Associations::HasOne
|
27
34
|
when ROM::SQL::Association::OneToMany
|
28
35
|
Associations::HasMany
|
29
36
|
when ROM::SQL::Association::ManyToOne
|
@@ -32,6 +39,7 @@ module Hanami
|
|
32
39
|
raise "Unsupported association: #{association}"
|
33
40
|
end
|
34
41
|
end
|
42
|
+
# rubocop:enable Metrics/MethodLength
|
35
43
|
end
|
36
44
|
end
|
37
45
|
end
|
@@ -5,14 +5,95 @@ module Hanami
|
|
5
5
|
module Associations
|
6
6
|
# Many-To-One association
|
7
7
|
#
|
8
|
-
# @since
|
8
|
+
# @since 1.1.0
|
9
9
|
# @api private
|
10
10
|
class BelongsTo
|
11
|
-
# @since
|
11
|
+
# @since 1.1.0
|
12
12
|
# @api private
|
13
13
|
def self.schema_type(entity)
|
14
14
|
Sql::Types::Schema::AssociationType.new(entity)
|
15
15
|
end
|
16
|
+
|
17
|
+
# @since 1.1.0
|
18
|
+
# @api private
|
19
|
+
attr_reader :repository
|
20
|
+
|
21
|
+
# @since 1.1.0
|
22
|
+
# @api private
|
23
|
+
attr_reader :source
|
24
|
+
|
25
|
+
# @since 1.1.0
|
26
|
+
# @api private
|
27
|
+
attr_reader :target
|
28
|
+
|
29
|
+
# @since 1.1.0
|
30
|
+
# @api private
|
31
|
+
attr_reader :subject
|
32
|
+
|
33
|
+
# @since 1.1.0
|
34
|
+
# @api private
|
35
|
+
attr_reader :scope
|
36
|
+
|
37
|
+
# @since 1.1.0
|
38
|
+
# @api private
|
39
|
+
def initialize(repository, source, target, subject, scope = nil)
|
40
|
+
@repository = repository
|
41
|
+
@source = source
|
42
|
+
@target = target
|
43
|
+
@subject = subject.to_hash unless subject.nil?
|
44
|
+
@scope = scope || _build_scope
|
45
|
+
freeze
|
46
|
+
end
|
47
|
+
|
48
|
+
# @since 1.1.0
|
49
|
+
# @api private
|
50
|
+
def one
|
51
|
+
scope.one
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# @since 1.1.0
|
57
|
+
# @api private
|
58
|
+
def container
|
59
|
+
repository.container
|
60
|
+
end
|
61
|
+
|
62
|
+
# @since 1.1.0
|
63
|
+
# @api private
|
64
|
+
def primary_key
|
65
|
+
association_keys.first
|
66
|
+
end
|
67
|
+
|
68
|
+
# @since 1.1.0
|
69
|
+
# @api private
|
70
|
+
def relation(name)
|
71
|
+
repository.relations[Hanami::Utils::String.pluralize(name)]
|
72
|
+
end
|
73
|
+
|
74
|
+
# @since 1.1.0
|
75
|
+
# @api private
|
76
|
+
def foreign_key
|
77
|
+
association_keys.last
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns primary key and foreign key
|
81
|
+
#
|
82
|
+
# @since 1.1.0
|
83
|
+
# @api private
|
84
|
+
def association_keys
|
85
|
+
relation(source)
|
86
|
+
.associations[target]
|
87
|
+
.__send__(:join_key_map, container.relations)
|
88
|
+
end
|
89
|
+
|
90
|
+
# @since 1.1.0
|
91
|
+
# @api private
|
92
|
+
def _build_scope
|
93
|
+
result = relation(target)
|
94
|
+
result = result.where(foreign_key => subject.fetch(primary_key)) unless subject.nil?
|
95
|
+
result.as(Model::MappedRelation.mapper_name)
|
96
|
+
end
|
16
97
|
end
|
17
98
|
end
|
18
99
|
end
|
@@ -15,13 +15,19 @@ module Hanami
|
|
15
15
|
|
16
16
|
# @since 0.7.0
|
17
17
|
# @api private
|
18
|
-
def has_many(relation,
|
18
|
+
def has_many(relation, **args)
|
19
19
|
@repository.__send__(:relations, relation)
|
20
|
+
@repository.__send__(:relations, args[:through]) if args[:through]
|
20
21
|
end
|
21
22
|
|
22
|
-
|
23
|
+
def has_one(relation, *)
|
24
|
+
@repository.__send__(:relations, Hanami::Utils::String.pluralize(relation).to_sym)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @since 1.1.0
|
23
28
|
# @api private
|
24
|
-
def belongs_to(*)
|
29
|
+
def belongs_to(relation, *)
|
30
|
+
@repository.__send__(:relations, Hanami::Utils::String.pluralize(relation).to_sym)
|
25
31
|
end
|
26
32
|
end
|
27
33
|
end
|
@@ -49,10 +49,10 @@ module Hanami
|
|
49
49
|
# @since 0.7.0
|
50
50
|
# @api private
|
51
51
|
def create(data)
|
52
|
-
entity.new(
|
53
|
-
|
54
|
-
|
55
|
-
)
|
52
|
+
entity.new(command(:create, aggregate(target), use: [:timestamps])
|
53
|
+
.call(data))
|
54
|
+
rescue => e
|
55
|
+
raise Hanami::Model::Error.for(e)
|
56
56
|
end
|
57
57
|
|
58
58
|
# @since 0.7.0
|
@@ -60,6 +60,8 @@ module Hanami
|
|
60
60
|
def add(data)
|
61
61
|
command(:create, relation(target), use: [:timestamps])
|
62
62
|
.call(associate(data))
|
63
|
+
rescue => e
|
64
|
+
raise Hanami::Model::Error.for(e)
|
63
65
|
end
|
64
66
|
|
65
67
|
# @since 0.7.0
|
@@ -0,0 +1,157 @@
|
|
1
|
+
module Hanami
|
2
|
+
module Model
|
3
|
+
module Associations
|
4
|
+
# Many-To-One association
|
5
|
+
#
|
6
|
+
# @since 1.1.0
|
7
|
+
# @api private
|
8
|
+
class HasOne
|
9
|
+
# @since 1.1.0
|
10
|
+
# @api private
|
11
|
+
def self.schema_type(entity)
|
12
|
+
Sql::Types::Schema::AssociationType.new(entity)
|
13
|
+
end
|
14
|
+
#
|
15
|
+
# @since 1.1.0
|
16
|
+
# @api private
|
17
|
+
attr_reader :repository
|
18
|
+
|
19
|
+
# @since 1.1.0
|
20
|
+
# @api private
|
21
|
+
attr_reader :source
|
22
|
+
|
23
|
+
# @since 1.1.0
|
24
|
+
# @api private
|
25
|
+
attr_reader :target
|
26
|
+
|
27
|
+
# @since 1.1.0
|
28
|
+
# @api private
|
29
|
+
attr_reader :subject
|
30
|
+
|
31
|
+
# @since 1.1.0
|
32
|
+
# @api private
|
33
|
+
attr_reader :scope
|
34
|
+
|
35
|
+
# @since 1.1.0
|
36
|
+
# @api private
|
37
|
+
def initialize(repository, source, target, subject, scope = nil)
|
38
|
+
@repository = repository
|
39
|
+
@source = source
|
40
|
+
@target = target
|
41
|
+
@subject = subject.to_hash unless subject.nil?
|
42
|
+
@scope = scope || _build_scope
|
43
|
+
freeze
|
44
|
+
end
|
45
|
+
|
46
|
+
def one
|
47
|
+
scope.one
|
48
|
+
end
|
49
|
+
|
50
|
+
def create(data)
|
51
|
+
entity.new(
|
52
|
+
command(:create, aggregate(target), use: [:timestamps]).call(data)
|
53
|
+
)
|
54
|
+
rescue => e
|
55
|
+
raise Hanami::Model::Error.for(e)
|
56
|
+
end
|
57
|
+
|
58
|
+
def add(data)
|
59
|
+
command(:create, relation(target), use: [:timestamps]).call(associate(data))
|
60
|
+
rescue => e
|
61
|
+
raise Hanami::Model::Error.for(e)
|
62
|
+
end
|
63
|
+
|
64
|
+
def update(data)
|
65
|
+
command(:update, relation(target), use: [:timestamps])
|
66
|
+
.by_pk(
|
67
|
+
one.public_send(relation(target).primary_key)
|
68
|
+
).call(data)
|
69
|
+
rescue => e
|
70
|
+
raise Hanami::Model::Error.for(e)
|
71
|
+
end
|
72
|
+
|
73
|
+
def delete
|
74
|
+
scope.delete
|
75
|
+
end
|
76
|
+
alias remove delete
|
77
|
+
|
78
|
+
def replace(data)
|
79
|
+
repository.transaction do
|
80
|
+
delete
|
81
|
+
add(data)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# @since 1.1.0
|
88
|
+
# @api private
|
89
|
+
def entity
|
90
|
+
repository.class.entity
|
91
|
+
end
|
92
|
+
|
93
|
+
# @since 1.1.0
|
94
|
+
# @api private
|
95
|
+
def aggregate(name)
|
96
|
+
repository.aggregate(name)
|
97
|
+
end
|
98
|
+
|
99
|
+
# @since 1.1.0
|
100
|
+
# @api private
|
101
|
+
def command(target, relation, options = {})
|
102
|
+
repository.command(target, relation, options)
|
103
|
+
end
|
104
|
+
|
105
|
+
# @since 1.1.0
|
106
|
+
# @api private
|
107
|
+
def relation(name)
|
108
|
+
repository.relations[Hanami::Utils::String.pluralize(name)]
|
109
|
+
end
|
110
|
+
|
111
|
+
# @since 1.1.0
|
112
|
+
# @api private
|
113
|
+
def container
|
114
|
+
repository.container
|
115
|
+
end
|
116
|
+
|
117
|
+
# @since 1.1.0
|
118
|
+
# @api private
|
119
|
+
def primary_key
|
120
|
+
association_keys.first
|
121
|
+
end
|
122
|
+
|
123
|
+
# @since 1.1.0
|
124
|
+
# @api private
|
125
|
+
def foreign_key
|
126
|
+
association_keys.last
|
127
|
+
end
|
128
|
+
|
129
|
+
# @since 1.1.0
|
130
|
+
# @api private
|
131
|
+
def associate(data)
|
132
|
+
relation(source)
|
133
|
+
.associations[target]
|
134
|
+
.associate(container.relations, data, subject)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Returns primary key and foreign key
|
138
|
+
#
|
139
|
+
# @since 1.1.0
|
140
|
+
# @api private
|
141
|
+
def association_keys
|
142
|
+
relation(source)
|
143
|
+
.associations[target]
|
144
|
+
.__send__(:join_key_map, container.relations)
|
145
|
+
end
|
146
|
+
|
147
|
+
# @since 1.1.0
|
148
|
+
# @api private
|
149
|
+
def _build_scope
|
150
|
+
result = relation(target)
|
151
|
+
result = result.where(foreign_key => subject.fetch(primary_key)) unless subject.nil?
|
152
|
+
result.as(Model::MappedRelation.mapper_name)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
module Hanami
|
2
|
+
module Model
|
3
|
+
module Associations
|
4
|
+
# Many-To-Many association
|
5
|
+
#
|
6
|
+
# @since 0.7.0
|
7
|
+
# @api private
|
8
|
+
class ManyToMany
|
9
|
+
# @since 0.7.0
|
10
|
+
# @api private
|
11
|
+
def self.schema_type(entity)
|
12
|
+
type = Sql::Types::Schema::AssociationType.new(entity)
|
13
|
+
Types::Strict::Array.member(type)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @since 1.1.0
|
17
|
+
# @api private
|
18
|
+
attr_reader :repository
|
19
|
+
|
20
|
+
# @since 1.1.0
|
21
|
+
# @api private
|
22
|
+
attr_reader :source
|
23
|
+
|
24
|
+
# @since 1.1.0
|
25
|
+
# @api private
|
26
|
+
attr_reader :target
|
27
|
+
|
28
|
+
# @since 1.1.0
|
29
|
+
# @api private
|
30
|
+
attr_reader :subject
|
31
|
+
|
32
|
+
# @since 1.1.0
|
33
|
+
# @api private
|
34
|
+
attr_reader :scope
|
35
|
+
|
36
|
+
# @since 1.1.0
|
37
|
+
# @api private
|
38
|
+
attr_reader :through
|
39
|
+
|
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
|
+
@through = relation(source).associations[target].through.to_sym
|
46
|
+
@scope = scope || _build_scope
|
47
|
+
freeze
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_a
|
51
|
+
scope.to_a
|
52
|
+
end
|
53
|
+
|
54
|
+
def map(&blk)
|
55
|
+
to_a.map(&blk)
|
56
|
+
end
|
57
|
+
|
58
|
+
def each(&blk)
|
59
|
+
scope.each(&blk)
|
60
|
+
end
|
61
|
+
|
62
|
+
def count
|
63
|
+
scope.count
|
64
|
+
end
|
65
|
+
|
66
|
+
def where(condition)
|
67
|
+
__new__(scope.where(condition))
|
68
|
+
end
|
69
|
+
|
70
|
+
# @since 1.1.0
|
71
|
+
# @api private
|
72
|
+
# Return the association table object. Would need an aditional query to return the entity
|
73
|
+
def add(*data)
|
74
|
+
command(:create, relation(through), use: [:timestamps])
|
75
|
+
.call(associate(data.map(&:to_h)))
|
76
|
+
rescue => e
|
77
|
+
raise Hanami::Model::Error.for(e)
|
78
|
+
end
|
79
|
+
|
80
|
+
# @since 1.1.0
|
81
|
+
# @api private
|
82
|
+
def delete
|
83
|
+
relation(through).where(source_foreign_key => subject.fetch(source_primary_key)).delete
|
84
|
+
end
|
85
|
+
|
86
|
+
# @since 1.1.0
|
87
|
+
# @api private
|
88
|
+
# rubocop:disable Metrics/AbcSize
|
89
|
+
def remove(target_id)
|
90
|
+
association_record = relation(through)
|
91
|
+
.where(target_foreign_key => target_id, source_foreign_key => subject.fetch(source_primary_key))
|
92
|
+
.one
|
93
|
+
if association_record
|
94
|
+
ar_id = association_record.public_send relation(through).primary_key
|
95
|
+
command(:delete, relation(through)).by_pk(ar_id).call
|
96
|
+
end
|
97
|
+
end
|
98
|
+
# rubocop:enable Metrics/AbcSize
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
# @since 1.1.0
|
103
|
+
# @api private
|
104
|
+
def container
|
105
|
+
repository.container
|
106
|
+
end
|
107
|
+
|
108
|
+
# @since 1.1.0
|
109
|
+
# @api private
|
110
|
+
def relation(name)
|
111
|
+
repository.relations[name]
|
112
|
+
end
|
113
|
+
|
114
|
+
# @since 1.1.0
|
115
|
+
# @api private
|
116
|
+
def command(target, relation, options = {})
|
117
|
+
repository.command(target, relation, options)
|
118
|
+
end
|
119
|
+
|
120
|
+
# @since 1.1.0
|
121
|
+
# @api private
|
122
|
+
def associate(data)
|
123
|
+
relation(target)
|
124
|
+
.associations[source]
|
125
|
+
.associate(container.relations, data, subject)
|
126
|
+
end
|
127
|
+
|
128
|
+
# @since 1.1.0
|
129
|
+
# @api private
|
130
|
+
def source_primary_key
|
131
|
+
association_keys[0].first
|
132
|
+
end
|
133
|
+
|
134
|
+
# @since 1.1.0
|
135
|
+
# @api private
|
136
|
+
def source_foreign_key
|
137
|
+
association_keys[0].last
|
138
|
+
end
|
139
|
+
|
140
|
+
# @since 1.1.0
|
141
|
+
# @api private
|
142
|
+
def association_keys
|
143
|
+
relation(source)
|
144
|
+
.associations[target]
|
145
|
+
.__send__(:join_key_map, container.relations)
|
146
|
+
end
|
147
|
+
|
148
|
+
# @since 1.1.0
|
149
|
+
# @api private
|
150
|
+
def target_foreign_key
|
151
|
+
association_keys[1].first
|
152
|
+
end
|
153
|
+
|
154
|
+
# @since 1.1.0
|
155
|
+
# @api private
|
156
|
+
def target_primary_key
|
157
|
+
association_keys[1].last
|
158
|
+
end
|
159
|
+
|
160
|
+
# @since 1.1.0
|
161
|
+
# @api private
|
162
|
+
# rubocop:disable Metrics/AbcSize
|
163
|
+
def _build_scope
|
164
|
+
result = relation(target).qualified
|
165
|
+
unless subject.nil?
|
166
|
+
result = result
|
167
|
+
.join(through, target_foreign_key => target_primary_key)
|
168
|
+
.where(source_foreign_key => subject.fetch(source_primary_key))
|
169
|
+
end
|
170
|
+
result.as(Model::MappedRelation.mapper_name)
|
171
|
+
end
|
172
|
+
# rubocop:enable Metrics/AbcSize
|
173
|
+
|
174
|
+
# @since 1.1.0
|
175
|
+
# @api private
|
176
|
+
def __new__(new_scope)
|
177
|
+
self.class.new(repository, source, target, subject, new_scope)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
data/lib/hanami/model/error.rb
CHANGED
@@ -60,7 +60,7 @@ module Hanami
|
|
60
60
|
# Error for Unique Constraint Violation
|
61
61
|
#
|
62
62
|
# @since 0.6.1
|
63
|
-
class UniqueConstraintViolationError <
|
63
|
+
class UniqueConstraintViolationError < ConstraintViolationError
|
64
64
|
# @since 0.6.1
|
65
65
|
# @api private
|
66
66
|
def initialize(message = 'Unique constraint has been violated')
|
@@ -71,7 +71,7 @@ module Hanami
|
|
71
71
|
# Error for Foreign Key Constraint Violation
|
72
72
|
#
|
73
73
|
# @since 0.6.1
|
74
|
-
class ForeignKeyConstraintViolationError <
|
74
|
+
class ForeignKeyConstraintViolationError < ConstraintViolationError
|
75
75
|
# @since 0.6.1
|
76
76
|
# @api private
|
77
77
|
def initialize(message = 'Foreign key constraint has been violated')
|
@@ -82,7 +82,7 @@ module Hanami
|
|
82
82
|
# Error for Not Null Constraint Violation
|
83
83
|
#
|
84
84
|
# @since 0.6.1
|
85
|
-
class NotNullConstraintViolationError <
|
85
|
+
class NotNullConstraintViolationError < ConstraintViolationError
|
86
86
|
# @since 0.6.1
|
87
87
|
# @api private
|
88
88
|
def initialize(message = 'NOT NULL constraint has been violated')
|
@@ -93,7 +93,7 @@ module Hanami
|
|
93
93
|
# Error for Check Constraint Violation raised by Sequel
|
94
94
|
#
|
95
95
|
# @since 0.6.1
|
96
|
-
class CheckConstraintViolationError <
|
96
|
+
class CheckConstraintViolationError < ConstraintViolationError
|
97
97
|
# @since 0.6.1
|
98
98
|
# @api private
|
99
99
|
def initialize(message = 'Check constraint has been violated')
|
@@ -116,7 +116,7 @@ module Hanami
|
|
116
116
|
# # Reads all files from "db/migrations" and apply them
|
117
117
|
# Hanami::Model::Migrator.migrate
|
118
118
|
#
|
119
|
-
# # Migrate to a
|
119
|
+
# # Migrate to a specifiy version
|
120
120
|
# Hanami::Model::Migrator.migrate(version: "20150610133853")
|
121
121
|
#
|
122
122
|
# NOTE: Class level interface SHOULD be removed in Hanami 2.0
|
@@ -189,7 +189,7 @@ module Hanami
|
|
189
189
|
# migrations 'db/migrations'
|
190
190
|
# end
|
191
191
|
#
|
192
|
-
# Hanami::Model::Migrator.prepare # => creates `foo' and
|
192
|
+
# Hanami::Model::Migrator.prepare # => creates `foo' and run migrations
|
193
193
|
#
|
194
194
|
# @example Prepare Database (with schema dump)
|
195
195
|
# require 'hanami/model'
|
@@ -123,10 +123,10 @@ module Hanami
|
|
123
123
|
# Returns a database connection
|
124
124
|
#
|
125
125
|
# Given a DB connection URI we can connect to a specific database or not, we need this when creating
|
126
|
-
# or
|
126
|
+
# or droping a database. Important to notice that we can't always open a _global_ DB connection,
|
127
127
|
# because most of the times application's DB user has no rights to do so.
|
128
128
|
#
|
129
|
-
# @param global [Boolean] determine whether or not a connection should specify
|
129
|
+
# @param global [Boolean] determine whether or not a connection should specify an database.
|
130
130
|
#
|
131
131
|
# @since 0.5.0
|
132
132
|
# @api private
|
@@ -33,15 +33,7 @@ module Hanami
|
|
33
33
|
def create
|
34
34
|
set_environment_variables
|
35
35
|
|
36
|
-
call_db_command('createdb')
|
37
|
-
message = if error_message.match(/already exists/) # rubocop:disable Performance/RedundantMatch
|
38
|
-
DB_CREATION_ERROR
|
39
|
-
else
|
40
|
-
error_message
|
41
|
-
end
|
42
|
-
|
43
|
-
raise MigrationError.new(message)
|
44
|
-
end
|
36
|
+
call_db_command('createdb')
|
45
37
|
end
|
46
38
|
|
47
39
|
# @since 0.4.0
|
@@ -49,15 +41,7 @@ module Hanami
|
|
49
41
|
def drop
|
50
42
|
set_environment_variables
|
51
43
|
|
52
|
-
call_db_command('dropdb')
|
53
|
-
message = if error_message.match(/does not exist/) # rubocop:disable Performance/RedundantMatch
|
54
|
-
"Cannot find database: #{database}"
|
55
|
-
else
|
56
|
-
error_message
|
57
|
-
end
|
58
|
-
|
59
|
-
raise MigrationError.new(message)
|
60
|
-
end
|
44
|
+
call_db_command('dropdb')
|
61
45
|
end
|
62
46
|
|
63
47
|
# @since 0.4.0
|
@@ -112,10 +96,27 @@ module Hanami
|
|
112
96
|
|
113
97
|
begin
|
114
98
|
Open3.popen3(command, database) do |_stdin, _stdout, stderr, wait_thr|
|
115
|
-
|
99
|
+
unless wait_thr.value.success? # wait_thr.value is the exit status
|
100
|
+
raise MigrationError.new(modified_message(stderr.read))
|
101
|
+
end
|
116
102
|
end
|
117
103
|
rescue SystemCallError => e
|
118
|
-
|
104
|
+
raise MigrationError.new(modified_message(e.message))
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# @since 1.1.0
|
109
|
+
# @api private
|
110
|
+
def modified_message(original_message)
|
111
|
+
case original_message
|
112
|
+
when /already exists/
|
113
|
+
DB_CREATION_ERROR
|
114
|
+
when /does not exist/
|
115
|
+
"Cannot find database: #{database}"
|
116
|
+
when /No such file or directory/
|
117
|
+
"Could not find executable in your PATH: `#{original_message.split.last}`"
|
118
|
+
else
|
119
|
+
original_message
|
119
120
|
end
|
120
121
|
end
|
121
122
|
end
|
data/lib/hanami/model/sql.rb
CHANGED
@@ -22,7 +22,7 @@ module Hanami
|
|
22
22
|
# associations and potentially to mapping defined by the repository.
|
23
23
|
#
|
24
24
|
# @param registry [Hash] a registry that keeps reference between
|
25
|
-
# entities
|
25
|
+
# entities klass and their underscored names
|
26
26
|
# @param relation [ROM::Relation] the database relation
|
27
27
|
# @param mapping [Hanami::Model::Mapping] the optional repository
|
28
28
|
# mapping
|
@@ -75,7 +75,7 @@ module Hanami
|
|
75
75
|
# Build the schema
|
76
76
|
#
|
77
77
|
# @param registry [Hash] a registry that keeps reference between
|
78
|
-
# entities
|
78
|
+
# entities klass and their underscored names
|
79
79
|
# @param relation [ROM::Relation] the database relation
|
80
80
|
# @param mapping [Hanami::Model::Mapping] the optional repository
|
81
81
|
# mapping
|
@@ -112,7 +112,7 @@ module Hanami
|
|
112
112
|
# Merge attributes and associations
|
113
113
|
#
|
114
114
|
# @param registry [Hash] a registry that keeps reference between
|
115
|
-
# entities
|
115
|
+
# entities klass and their underscored names
|
116
116
|
# @param associations [ROM::AssociationSet] a set of associations for
|
117
117
|
# the current relation
|
118
118
|
#
|
@@ -70,7 +70,7 @@ module Hanami
|
|
70
70
|
|
71
71
|
# NOTE: In the future rom-sql should be able to always return Ruby
|
72
72
|
# types instead of Sequel types. When that will happen we can get
|
73
|
-
# rid of this logic in the block and
|
73
|
+
# rid of this logic in the block and to fallback to:
|
74
74
|
#
|
75
75
|
# MAPPING.fetch(unwrapped.pristine, attribute)
|
76
76
|
MAPPING.fetch(unwrapped.pristine) do
|
@@ -82,21 +82,11 @@ module Hanami
|
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
85
|
-
# @since 1.0.4
|
86
|
-
# @api private
|
87
|
-
def self.pg_json_pristines
|
88
|
-
@pg_json_pristines ||= ::Hash.new do |hash, type|
|
89
|
-
hash[type] = if defined?(ROM::SQL::Types::PG)
|
90
|
-
ROM::SQL::Types::PG.const_get(type).pristine
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
85
|
# @since 1.0.2
|
96
86
|
# @api private
|
97
87
|
def self.pg_json?(pristine)
|
98
|
-
pristine ==
|
99
|
-
pristine ==
|
88
|
+
(defined?(ROM::SQL::Types::PG::JSONB) && pristine == ROM::SQL::Types::PG::JSONB) ||
|
89
|
+
(defined?(ROM::SQL::Types::PG::JSON) && pristine == ROM::SQL::Types::PG::JSON)
|
100
90
|
end
|
101
91
|
|
102
92
|
private_class_method :pg_json?
|
data/lib/hanami/model/types.rb
CHANGED
@@ -47,6 +47,7 @@ module Hanami
|
|
47
47
|
# @since 0.7.0
|
48
48
|
# @api private
|
49
49
|
def call(value)
|
50
|
+
return if value.nil?
|
50
51
|
if valid?(value)
|
51
52
|
coerce(value)
|
52
53
|
else
|
@@ -57,7 +58,7 @@ module Hanami
|
|
57
58
|
# Check if value can be coerced
|
58
59
|
#
|
59
60
|
# It is true if value is an instance of `object` type or if value
|
60
|
-
#
|
61
|
+
# respond to `#to_hash`.
|
61
62
|
#
|
62
63
|
# @param value [Object] the value
|
63
64
|
#
|
data/lib/hanami/model/version.rb
CHANGED
data/lib/hanami/repository.rb
CHANGED
@@ -54,7 +54,7 @@ module Hanami
|
|
54
54
|
#
|
55
55
|
# All the queries and commands are private.
|
56
56
|
# This decision forces developers to define intention revealing API, instead
|
57
|
-
#
|
57
|
+
# leak storage API details outside of a repository.
|
58
58
|
#
|
59
59
|
# @example
|
60
60
|
# require 'hanami/model'
|
@@ -87,7 +87,7 @@ module Hanami
|
|
87
87
|
# # * It expresses a clear intent.
|
88
88
|
# #
|
89
89
|
# # * The caller can be easily tested in isolation.
|
90
|
-
# # It's just a matter of
|
90
|
+
# # It's just a matter of stub this method.
|
91
91
|
# #
|
92
92
|
# # * If we change the storage, the callers aren't affected.
|
93
93
|
#
|
@@ -167,7 +167,7 @@ module Hanami
|
|
167
167
|
# rubocop:enable Metrics/AbcSize
|
168
168
|
# rubocop:enable Metrics/MethodLength
|
169
169
|
|
170
|
-
# Defines the
|
170
|
+
# Defines the ampping between a database table and an entity.
|
171
171
|
#
|
172
172
|
# It's also responsible to associate table columns to entity attributes.
|
173
173
|
#
|
@@ -314,7 +314,7 @@ module Hanami
|
|
314
314
|
module Commands
|
315
315
|
# Create a new record
|
316
316
|
#
|
317
|
-
# @return [Hanami::Entity]
|
317
|
+
# @return [Hanami::Entity] an new created entity
|
318
318
|
#
|
319
319
|
# @raise [Hanami::Model::Error] an error in case the command fails
|
320
320
|
#
|
metadata
CHANGED
@@ -1,49 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hanami-model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.1.0.beta1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Luca Guidi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-08-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hanami-utils
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '1.0'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '1.0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: rom
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '3.3'
|
34
|
-
- - ">="
|
17
|
+
- - '='
|
35
18
|
- !ruby/object:Gem::Version
|
36
|
-
version:
|
19
|
+
version: 1.1.0.beta1
|
37
20
|
type: :runtime
|
38
21
|
prerelease: false
|
39
22
|
version_requirements: !ruby/object:Gem::Requirement
|
40
23
|
requirements:
|
41
|
-
- -
|
42
|
-
- !ruby/object:Gem::Version
|
43
|
-
version: '3.3'
|
44
|
-
- - ">="
|
24
|
+
- - '='
|
45
25
|
- !ruby/object:Gem::Version
|
46
|
-
version:
|
26
|
+
version: 1.1.0.beta1
|
47
27
|
- !ruby/object:Gem::Dependency
|
48
28
|
name: rom-sql
|
49
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -51,9 +31,6 @@ dependencies:
|
|
51
31
|
- - "~>"
|
52
32
|
- !ruby/object:Gem::Version
|
53
33
|
version: '1.3'
|
54
|
-
- - ">="
|
55
|
-
- !ruby/object:Gem::Version
|
56
|
-
version: 1.3.5
|
57
34
|
type: :runtime
|
58
35
|
prerelease: false
|
59
36
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -61,9 +38,6 @@ dependencies:
|
|
61
38
|
- - "~>"
|
62
39
|
- !ruby/object:Gem::Version
|
63
40
|
version: '1.3'
|
64
|
-
- - ">="
|
65
|
-
- !ruby/object:Gem::Version
|
66
|
-
version: 1.3.5
|
67
41
|
- !ruby/object:Gem::Dependency
|
68
42
|
name: rom-repository
|
69
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -84,14 +58,14 @@ dependencies:
|
|
84
58
|
requirements:
|
85
59
|
- - "~>"
|
86
60
|
- !ruby/object:Gem::Version
|
87
|
-
version: 0.11
|
61
|
+
version: '0.11'
|
88
62
|
type: :runtime
|
89
63
|
prerelease: false
|
90
64
|
version_requirements: !ruby/object:Gem::Requirement
|
91
65
|
requirements:
|
92
66
|
- - "~>"
|
93
67
|
- !ruby/object:Gem::Version
|
94
|
-
version: 0.11
|
68
|
+
version: '0.11'
|
95
69
|
- !ruby/object:Gem::Dependency
|
96
70
|
name: concurrent-ruby
|
97
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -167,6 +141,8 @@ files:
|
|
167
141
|
- lib/hanami/model/associations/belongs_to.rb
|
168
142
|
- lib/hanami/model/associations/dsl.rb
|
169
143
|
- lib/hanami/model/associations/has_many.rb
|
144
|
+
- lib/hanami/model/associations/has_one.rb
|
145
|
+
- lib/hanami/model/associations/many_to_many.rb
|
170
146
|
- lib/hanami/model/configuration.rb
|
171
147
|
- lib/hanami/model/configurator.rb
|
172
148
|
- lib/hanami/model/entity_name.rb
|
@@ -213,12 +189,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
213
189
|
version: 2.3.0
|
214
190
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
215
191
|
requirements:
|
216
|
-
- - "
|
192
|
+
- - ">"
|
217
193
|
- !ruby/object:Gem::Version
|
218
|
-
version:
|
194
|
+
version: 1.3.1
|
219
195
|
requirements: []
|
220
196
|
rubyforge_project:
|
221
|
-
rubygems_version: 2.6.
|
197
|
+
rubygems_version: 2.6.11
|
222
198
|
signing_key:
|
223
199
|
specification_version: 4
|
224
200
|
summary: A persistence layer for Hanami
|