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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4a4972da9eec643eb9b1ff52fe1d2f6a626aba4b
4
- data.tar.gz: 8dc59827cda2726dd50fb02c0ef3cc3488943613
3
+ metadata.gz: 8c17cc417f16015bc4dafe69467c74e94198a10a
4
+ data.tar.gz: f18523f952dce748ae2eeba9c6cd4c206236d1cb
5
5
  SHA512:
6
- metadata.gz: 1418bf7590d149a90abbd463e728f5a20ab3417fddbfe4fc8a46f61205700224de6fa2a0c685a102575727a6fca821067dd3b739c93fba8d9886f71afa6de8f2
7
- data.tar.gz: 2de56a5ddd4f1705b553a953ac47c1fe862217e553fb9a2158492f426f234ac6959895403517343d4558fd8c220c57dc8a58106e56e0502278919f57c76aaada
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.4 - 2017-10-14
5
- ### Fixed
6
- - [Nikita Shilnikov] Keep the dependency on `dry-sql` at `~> 1.3`, which is compatible with `dry-types` `~> 0.11.0`
7
- - [Nikita Shilnikov] Ensure to write Postgres JSON (`PGJSON`) type for nested associated records
8
- - [Nikita Shilnikov] Ensure `Repository#select` to work with `Hanami::Model::MappedRelation`
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
- - [Luca Guidi] Keep the dependency on `dry-types` at `~> 0.11.0`
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://coveralls.io/repos/github/hanami/model/badge.svg?branch=master)](https://coveralls.io/github/hanami/model?branch=master)
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', '~> 1.0'
24
- spec.add_runtime_dependency 'rom', '~> 3.3', '>= 3.3.2'
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.0'
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
 
@@ -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.schema(attributes)]
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 rebooting applications in production and to ensure that
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 0.7.0
8
+ # @since 1.1.0
9
9
  # @api private
10
10
  class BelongsTo
11
- # @since 0.7.0
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
- # @since 0.7.0
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
- command(:create, aggregate(target), use: [:timestamps])
54
- .call(data)
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
@@ -24,7 +24,7 @@ module Hanami
24
24
  # @since 0.7.0
25
25
  # @api private
26
26
  def underscore
27
- Utils::String.new(@name).underscore.to_sym
27
+ Utils::String.underscore(@name).to_sym
28
28
  end
29
29
 
30
30
  # @since 0.7.0
@@ -60,7 +60,7 @@ module Hanami
60
60
  # Error for Unique Constraint Violation
61
61
  #
62
62
  # @since 0.6.1
63
- class UniqueConstraintViolationError < Error
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 < Error
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 < Error
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 < Error
96
+ class CheckConstraintViolationError < ConstraintViolationError
97
97
  # @since 0.6.1
98
98
  # @api private
99
99
  def initialize(message = 'Check constraint has been violated')
@@ -2,7 +2,7 @@ module Hanami
2
2
  module Model
3
3
  # Mapped proxy for ROM relations.
4
4
  #
5
- # It eliminates the need to use #as for repository queries
5
+ # It eliminates the need of use #as for repository queries
6
6
  #
7
7
  # @since 1.0.0
8
8
  # @api private
@@ -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 specific version
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 runs migrations
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 dropping a database. Important to notice that we can't always open a _global_ DB connection,
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 a database.
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') do |error_message|
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') do |error_message|
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
- yield stderr.read unless wait_thr.value.success? # wait_thr.value is the exit status
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
- yield e.message
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
@@ -23,7 +23,7 @@ module Hanami
23
23
  # @since 0.7.0
24
24
  # @api private
25
25
  def [](value)
26
- @input[@mapping.process(value)]
26
+ @mapping.process(@input[value])
27
27
  end
28
28
  end
29
29
 
@@ -31,7 +31,7 @@ module Hanami
31
31
  # @since 0.7.0
32
32
  # @api private
33
33
  def [](value)
34
- return @input[value] unless timestamps?
34
+ return value unless timestamps?
35
35
  _touch(@input[value], Time.now)
36
36
  end
37
37
 
@@ -17,7 +17,7 @@ module Hanami
17
17
  # @since 0.7.0
18
18
  # @api private
19
19
  def self.new(name)
20
- Utils::String.new(super).underscore.pluralize
20
+ Utils::String.transform(super, :underscore, :pluralize)
21
21
  end
22
22
  end
23
23
  end
@@ -110,7 +110,7 @@ module Hanami
110
110
  # end
111
111
  #
112
112
  # down do
113
- # drop_table :items
113
+ # drop_table :itmes
114
114
  # execute 'DROP TYPE inventory_item'
115
115
  # end
116
116
  # end
@@ -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 class and their underscored names
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 class and their underscored names
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 class and their underscored names
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 fall back to:
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 == pg_json_pristines['JSONB'.freeze] ||
99
- pristine == pg_json_pristines['JSON'.freeze]
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?
@@ -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
- # responds to `#to_hash`.
61
+ # respond to `#to_hash`.
61
62
  #
62
63
  # @param value [Object] the value
63
64
  #
@@ -3,6 +3,6 @@ module Hanami
3
3
  # Defines the version
4
4
  #
5
5
  # @since 0.1.0
6
- VERSION = '1.0.4'.freeze
6
+ VERSION = '1.1.0.beta1'.freeze
7
7
  end
8
8
  end
@@ -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
- # of leaking storage API details outside of a repository.
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 stubbing this method.
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 mapping between a database table and an entity.
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] a new created 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
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-10-14 00:00:00.000000000 Z
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: 3.3.2
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: 3.3.2
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.0
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.0
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: '0'
194
+ version: 1.3.1
219
195
  requirements: []
220
196
  rubyforge_project:
221
- rubygems_version: 2.6.13
197
+ rubygems_version: 2.6.11
222
198
  signing_key:
223
199
  specification_version: 4
224
200
  summary: A persistence layer for Hanami