hanami-model 0.7.0 → 1.0.0.beta1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ac96a7e41555f5a379c12d3fd45c999ea4ce7e93
4
- data.tar.gz: 59f4d8fe809cd3536bc47fff73830ba37304a6c7
3
+ metadata.gz: 396fbb6a95d9e5526ff1e2415246d0bebc3f8413
4
+ data.tar.gz: 67a3c00a1e9c29ced838ba349082db34262edd1d
5
5
  SHA512:
6
- metadata.gz: f730a23b1a29d426405c1cb79ee2ea74ab73f9baaa73521c7136b3f39913a4a9dcc6cdeed2e45ce4ffd2b334fbb839e637b8ff60edbafa19777d47fb9e5c505d
7
- data.tar.gz: 382a94c09b68ed787d67643b6128ce1d2f54b0d2c669bc2c0248ad45c8e54fbfe14bd613bb9e9268988d16dcc29f2dd05c30be08c7461ec1a30b11fe34f9f635
6
+ metadata.gz: b47a74240ea4e92273809b248531ce3d06b301b4e6ab5dc9e588f8bd1297157d368b1ee4725281a401f578db1fbf17a698be2a03844718c8f7c73c594e19288b
7
+ data.tar.gz: 9307402f7125c83e3d9e3e1b1365ffb7cdcb661167bc1348969e52d13658edbf0ba28110e4923b1da4a1461554ee308564a9b6853b2240633ffb4c517975677f
data/CHANGELOG.md CHANGED
@@ -1,6 +1,24 @@
1
1
  # Hanami::Model
2
2
  A persistence layer for Hanami
3
3
 
4
+ ## v1.0.0.beta1 - 2017-02-14
5
+ ### Added
6
+ - [Luca Guidi] Official support for Ruby: MRI 2.4
7
+ - [Luca Guidi] Introduced `Repository#read` to fetch from database with raw SQL string
8
+ - [Luca Guidi] Introduced `Repository.schema` to manually configure the schema of a database table. This is useful for legacy databases where Hanami::Model autoinferring doesn't map correctly the schema.
9
+ - [Luca Guidi & Alfonso Uceda] Added `Hanami::Model::Configuration#gateway` to configure gateway and the raw connection
10
+ - [Luca Guidi] Added `Hanami::Model::Configuration#logger` to configure a logger
11
+ - [Luca Guidi] Database operations (including migrations) print informations to standard output
12
+
13
+ ### Fixed
14
+ - [Thorbjørn Hermansen] Ensure repository to not override given timestamps
15
+ - [Luca Guidi] Raise `Hanami::Model::MissingPrimaryKeyError` if `Repository#find` is ran against a database w/o a primary key
16
+ - [Alfonso Uceda] Ensure SQLite databases to be used on JRuby when the database path is in the same directory of the Ruby script (eg. `./test.sqlite`)
17
+
18
+ ### Changed
19
+ - [Luca Guidi] Automap the main relation in a repository, by removing the need of use `.as(:entity)`
20
+ - [Luca Guidi] Raise an `Hanami::Model::UnknownDatabaseTypeError` when the application is loaded and there is an unknown column type in the database
21
+
4
22
  ## v0.7.0 - 2016-11-15
5
23
  ### Added
6
24
  - [Luca Guidi] `Hanami::Entity` defines an automatic schema for SQL databases
data/README.md CHANGED
@@ -127,7 +127,7 @@ This architecture has several advantages:
127
127
 
128
128
  * Multiple data sources can easily coexist in an application
129
129
 
130
- When a class includes `Hanami::Repository`, it will receive the following interface:
130
+ When a class inherits from `Hanami::Repository`, it will receive the following interface:
131
131
 
132
132
  * `#create(data)` – Create a record for the given data (or entity)
133
133
  * `#update(id, data)` – Update the record corresponding to the given id by setting the given data (or entity)
@@ -207,7 +207,7 @@ class UserRepository < Hanami::Repository
207
207
  end
208
208
  end
209
209
  ```
210
- **NOTE:** This feature should be used only when **_automapping_** fails because the naming mismatch.
210
+ **NOTE:** This feature should be used only when **_automapping_** fails because of the naming mismatch.
211
211
 
212
212
  ### Conventions
213
213
 
@@ -216,7 +216,7 @@ end
216
216
  ### Thread safety
217
217
 
218
218
  **Hanami::Model**'s is thread safe during the runtime, but it isn't during the loading process.
219
- The mapper compiles some code internally, be sure to safely load it before your application starts.
219
+ The mapper compiles some code internally, so be sure to safely load it before your application starts.
220
220
 
221
221
  ```ruby
222
222
  Mutex.new.synchronize do
@@ -224,7 +224,7 @@ Mutex.new.synchronize do
224
224
  end
225
225
  ```
226
226
 
227
- **This is not necessary, when Hanami::Model is used within a Hanami application.**
227
+ **This is not necessary when Hanami::Model is used within a Hanami application.**
228
228
 
229
229
  ## Features
230
230
 
@@ -258,6 +258,25 @@ puts user.created_at.to_s # => "2016-09-19 13:40:13 UTC"
258
258
  puts user.updated_at.to_s # => "2016-09-19 13:40:16 UTC"
259
259
  ```
260
260
 
261
+ ## Configuration
262
+
263
+ ### Logging
264
+
265
+ In order to log database operations, you can configure a logger:
266
+
267
+ ```ruby
268
+ Hanami::Model.configure do
269
+ # ...
270
+ logger "log/development.log", level: :debug
271
+ end
272
+ ```
273
+
274
+ It accepts the following arguments:
275
+
276
+ * `stream`: a Ruby StringIO object - it can be `$stdout` or a path to file (eg. `"log/development.log"`) - Defaults to `$stdout`
277
+ * `:level`: logging level - it can be: `:debug`, `:info`, `:warn`, `:error`, `:fatal`, `:unknown` - Defaults to `:debug`
278
+ * `:formatter`: logging formatter - it can be: `:default` or `:json` - Defaults to `:default`
279
+
261
280
  ## Versioning
262
281
 
263
282
  __Hanami::Model__ uses [Semantic Versioning 2.0.0](http://semver.org)
@@ -272,6 +291,6 @@ __Hanami::Model__ uses [Semantic Versioning 2.0.0](http://semver.org)
272
291
 
273
292
  ## Copyright
274
293
 
275
- Copyright © 2014-2016 Luca Guidi – Released under MIT License
294
+ Copyright © 2014-2017 Luca Guidi – Released under MIT License
276
295
 
277
296
  This project was formerly known as Lotus (`lotus-model`).
data/hanami-model.gemspec CHANGED
@@ -19,9 +19,9 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ['lib']
20
20
  spec.required_ruby_version = '>= 2.3.0'
21
21
 
22
- spec.add_runtime_dependency 'hanami-utils', '~> 0.8'
23
- spec.add_runtime_dependency 'rom-sql', '~> 0.9'
24
- spec.add_runtime_dependency 'rom-repository', '~> 0.3'
22
+ spec.add_runtime_dependency 'hanami-utils', '~> 1.0.0.beta'
23
+ spec.add_runtime_dependency 'rom-sql', '~> 1.0'
24
+ spec.add_runtime_dependency 'rom-repository', '~> 1.0'
25
25
  spec.add_runtime_dependency 'dry-types', '~> 0.9'
26
26
  spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
27
27
 
data/lib/hanami/entity.rb CHANGED
@@ -141,7 +141,7 @@ module Hanami
141
141
  #
142
142
  # @since 0.7.0
143
143
  def method_missing(m, *)
144
- attribute?(m) or super # rubocop:disable Style/AndOr
144
+ attribute?(m) or super
145
145
  attributes.fetch(m, nil)
146
146
  end
147
147
 
@@ -207,7 +207,7 @@ module Hanami
207
207
  def call(attributes)
208
208
  Utils::Hash.new(
209
209
  schema.call(attributes)
210
- ).symbolize!
210
+ ).deep_symbolize!
211
211
  end
212
212
 
213
213
  # @since 0.7.0
data/lib/hanami/model.rb CHANGED
@@ -17,7 +17,7 @@ module Hanami
17
17
 
18
18
  # @api private
19
19
  # @since 0.7.0
20
- @__repositories__ = Concurrent::Array.new # rubocop:disable Style/VariableNumber
20
+ @__repositories__ = Concurrent::Array.new
21
21
 
22
22
  class << self
23
23
  # @since 0.7.0
@@ -72,15 +72,9 @@ module Hanami
72
72
  end
73
73
 
74
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)
82
-
83
- @loaded = true
75
+ def self.load!(&blk)
76
+ @container = configuration.load!(repositories, &blk)
77
+ @loaded = true
84
78
  end
85
79
  end
86
80
  end
@@ -186,7 +186,7 @@ module Hanami
186
186
  def _build_scope
187
187
  result = relation(target)
188
188
  result = result.where(foreign_key => subject.fetch(primary_key)) unless subject.nil?
189
- result.as(Repository::MAPPER_NAME)
189
+ result.as(Model::MappedRelation.mapper_name)
190
190
  end
191
191
 
192
192
  # @since 0.7.0
@@ -8,7 +8,7 @@ module Hanami
8
8
  # via `Hanami::Model.configure`.
9
9
  #
10
10
  # @since 0.2.0
11
- class Configuration < ROM::Configuration
11
+ class Configuration
12
12
  # @since 0.7.0
13
13
  # @api private
14
14
  attr_reader :mappings
@@ -17,23 +17,33 @@ module Hanami
17
17
  # @api private
18
18
  attr_reader :entities
19
19
 
20
+ # @since 1.0.0.beta1
21
+ # @api private
22
+ attr_reader :logger
23
+
24
+ # @since 1.0.0.beta1
25
+ # @api private
26
+ attr_reader :migrations_logger
27
+
20
28
  # @since 0.2.0
21
29
  # @api private
22
30
  def initialize(configurator)
23
- super(configurator.backend, configurator.url)
24
- @migrations = configurator._migrations
25
- @schema = configurator._schema
26
- @mappings = {}
27
- @entities = {}
31
+ @backend = configurator.backend
32
+ @url = configurator.url
33
+ @migrations = configurator._migrations
34
+ @schema = configurator._schema
35
+ @gateway_config = configurator._gateway
36
+ @logger = configurator._logger
37
+ @migrations_logger = configurator.migrations_logger
38
+ @mappings = {}
39
+ @entities = {}
28
40
  end
29
41
 
30
42
  # NOTE: This must be changed when we want to support several adapters at the time
31
43
  #
32
44
  # @since 0.7.0
33
45
  # @api private
34
- def url
35
- connection.url
36
- end
46
+ attr_reader :url
37
47
 
38
48
  # NOTE: This must be changed when we want to support several adapters at the time
39
49
  #
@@ -48,7 +58,7 @@ module Hanami
48
58
  # @since 0.7.0
49
59
  # @api private
50
60
  def gateway
51
- environment.gateways[:default]
61
+ gateways[:default]
52
62
  end
53
63
 
54
64
  # Root directory
@@ -98,6 +108,51 @@ module Hanami
98
108
  entity.schema = Sql::Entity::Schema.new(entities, container.relations[relation], mappings.fetch(relation))
99
109
  end
100
110
  end
111
+
112
+ # @since 1.0.0.beta1
113
+ # @api private
114
+ def configure_gateway
115
+ @gateway_config.call(gateway) unless @gateway_config.nil?
116
+ end
117
+
118
+ # @since 1.0.0.beta1
119
+ # @api private
120
+ def logger=(value)
121
+ return if value.nil?
122
+ gateway.use_logger(@logger = value)
123
+ end
124
+
125
+ # @since 1.0.0.beta1
126
+ # @api private
127
+ def rom
128
+ @rom ||= ROM::Configuration.new(@backend, @url, infer_relations: false)
129
+ end
130
+
131
+ # @since 1.0.0.beta1
132
+ # @api private
133
+ def load!(repositories, &blk) # rubocop:disable Metrics/AbcSize
134
+ rom.setup.auto_registration(config.directory.to_s) unless config.directory.nil?
135
+ rom.instance_eval(&blk) if block_given?
136
+ configure_gateway
137
+ repositories.each(&:load!)
138
+ self.logger = logger
139
+
140
+ container = ROM.container(rom)
141
+ define_entities_mappings(container, repositories)
142
+ container
143
+ rescue => e
144
+ raise Hanami::Model::Error.for(e)
145
+ end
146
+
147
+ # @since 1.0.0.beta1
148
+ # @api private
149
+ def method_missing(method_name, *args, &blk)
150
+ if rom.respond_to?(method_name)
151
+ rom.__send__(method_name, *args, &blk)
152
+ else
153
+ super
154
+ end
155
+ end
101
156
  end
102
157
  end
103
158
  end
@@ -25,12 +25,27 @@ module Hanami
25
25
  # @api private
26
26
  attr_reader :_schema
27
27
 
28
+ # @since 1.0.0.beta1
29
+ # @api private
30
+ attr_reader :_logger
31
+
32
+ # @since 1.0.0.beta1
33
+ # @api private
34
+ attr_reader :_gateway
35
+
28
36
  # @since 0.7.0
29
37
  # @api private
30
38
  def self.build(&block)
31
39
  new.tap { |config| config.instance_eval(&block) }
32
40
  end
33
41
 
42
+ # @since 1.0.0.beta1
43
+ # @api private
44
+ def migrations_logger(stream = $stdout)
45
+ require 'hanami/model/migrator/logger'
46
+ @migrations_logger ||= Hanami::Model::Migrator::Logger.new(stream)
47
+ end
48
+
34
49
  private
35
50
 
36
51
  # @since 0.7.0
@@ -57,6 +72,21 @@ module Hanami
57
72
  def schema(path)
58
73
  @_schema = path
59
74
  end
75
+
76
+ # @since 1.0.0.beta1
77
+ # @api private
78
+ def logger(stream, options = {})
79
+ require 'hanami/logger'
80
+
81
+ opts = options.merge(stream: stream)
82
+ @_logger = Hanami::Logger.new('hanami.model', opts)
83
+ end
84
+
85
+ # @since 1.0.0.beta1
86
+ # @api private
87
+ def gateway(&blk)
88
+ @_gateway = blk
89
+ end
60
90
  end
61
91
  end
62
92
  end
@@ -8,7 +8,7 @@ module Hanami
8
8
  class Error < ::StandardError
9
9
  # @api private
10
10
  # @since 0.7.0
11
- @__mapping__ = Concurrent::Map.new # rubocop:disable Style/VariableNumber
11
+ @__mapping__ = Concurrent::Map.new
12
12
 
13
13
  # @api private
14
14
  # @since 0.7.0
@@ -88,5 +88,17 @@ module Hanami
88
88
  super
89
89
  end
90
90
  end
91
+
92
+ # Unknown database type error for repository auto-mapping
93
+ #
94
+ # @since 1.0.0.beta1
95
+ class UnknownDatabaseTypeError < Error
96
+ end
97
+
98
+ # Unknown primary key error
99
+ #
100
+ # @since 1.0.0.beta1
101
+ class MissingPrimaryKeyError < Error
102
+ end
91
103
  end
92
104
  end
@@ -0,0 +1,33 @@
1
+ module Hanami
2
+ module Model
3
+ # Mapped proxy for ROM relations.
4
+ #
5
+ # It eliminates the need of use #as for repository queries
6
+ #
7
+ # @since 1.0.0.beta1
8
+ # @api private
9
+ class MappedRelation < SimpleDelegator
10
+ # Mapper name.
11
+ #
12
+ # With ROM mapping there is a link between the entity class and a generic
13
+ # reference for it. Example: <tt>BookRepository</tt> references <tt>Book</tt>
14
+ # as <tt>:entity</tt>.
15
+ #
16
+ # @since 1.0.0.beta1
17
+ # @api private
18
+ MAPPER_NAME = :entity
19
+
20
+ # @since 1.0.0.beta1
21
+ # @api private
22
+ def self.mapper_name
23
+ MAPPER_NAME
24
+ end
25
+
26
+ # @since 1.0.0.beta1
27
+ # @api private
28
+ def initialize(relation)
29
+ super(relation.as(self.class.mapper_name))
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,4 +1,4 @@
1
- require 'transproc'
1
+ require 'transproc/all'
2
2
 
3
3
  module Hanami
4
4
  module Model
@@ -6,11 +6,19 @@ module Hanami
6
6
  #
7
7
  # @since 0.1.0
8
8
  class Mapping
9
+ extend Transproc::Registry
10
+
11
+ import Transproc::HashTransformations
12
+
9
13
  def initialize(&blk)
10
14
  @attributes = {}
11
15
  @r_attributes = {}
12
16
  instance_eval(&blk)
13
- @processor = @attributes.empty? ? ::Hash : Transproc(:rename_keys, @attributes)
17
+ @processor = @attributes.empty? ? ::Hash : t(:rename_keys, @attributes)
18
+ end
19
+
20
+ def t(name, *args)
21
+ self.class[name, *args]
14
22
  end
15
23
 
16
24
  def model(entity)
@@ -26,7 +26,7 @@ module Hanami
26
26
  # @since 0.4.0
27
27
  # @api private
28
28
  def self.for(configuration) # rubocop:disable Metrics/MethodLength
29
- connection = connection_for(configuration)
29
+ connection = Connection.new(configuration)
30
30
 
31
31
  case connection.database_type
32
32
  when :sqlite
@@ -40,30 +40,15 @@ module Hanami
40
40
  MySQLAdapter
41
41
  else
42
42
  self
43
- end.new(connection, configuration)
44
- end
45
-
46
- class << self
47
- private
48
-
49
- # @since 0.7.0
50
- # @api private
51
- def connection_for(configuration)
52
- Sequel.connect(
53
- configuration.url
54
- )
55
- rescue Sequel::AdapterNotFound
56
- raise MigrationError.new("Current adapter (#{configuration.adapter.type}) doesn't support SQL database operations.")
57
- end
43
+ end.new(connection)
58
44
  end
59
45
 
60
46
  # Initialize an adapter
61
47
  #
62
48
  # @since 0.4.0
63
49
  # @api private
64
- def initialize(connection, configuration)
65
- @connection = Connection.new(connection)
66
- @schema = configuration.schema
50
+ def initialize(connection)
51
+ @connection = connection
67
52
  end
68
53
 
69
54
  # Create database.
@@ -128,7 +113,9 @@ module Hanami
128
113
 
129
114
  # @since 0.4.0
130
115
  # @api private
131
- attr_reader :schema
116
+ def schema
117
+ connection.schema
118
+ end
132
119
 
133
120
  # Returns a database connection
134
121
  #
@@ -8,14 +8,23 @@ module Hanami
8
8
  # @since 0.5.0
9
9
  # @api private
10
10
  class Connection
11
- # @since 0.7.0
11
+ # @since 0.5.0
12
12
  # @api private
13
- attr_reader :raw
13
+ def initialize(configuration)
14
+ @configuration = configuration
15
+ end
14
16
 
15
- # @since 0.5.0
17
+ # @since 0.7.0
16
18
  # @api private
17
- def initialize(raw)
18
- @raw = raw
19
+ def raw
20
+ @raw ||= begin
21
+ Sequel.connect(
22
+ configuration.url,
23
+ loggers: [configuration.migrations_logger]
24
+ )
25
+ rescue Sequel::AdapterNotFound
26
+ raise MigrationError.new("Current adapter (#{configuration.adapter.type}) doesn't support SQL database operations.")
27
+ end
19
28
  end
20
29
 
21
30
  # Returns DB connection host
@@ -25,7 +34,7 @@ module Hanami
25
34
  # @since 0.5.0
26
35
  # @api private
27
36
  def host
28
- @host ||= opts.fetch(:host, parsed_uri.host)
37
+ @host ||= parsed_uri.host
29
38
  end
30
39
 
31
40
  # Returns DB connection port
@@ -35,7 +44,7 @@ module Hanami
35
44
  # @since 0.5.0
36
45
  # @api private
37
46
  def port
38
- @port ||= opts.fetch(:port, parsed_uri.port)
47
+ @port ||= parsed_uri.port
39
48
  end
40
49
 
41
50
  # Returns DB name from conenction
@@ -45,7 +54,7 @@ module Hanami
45
54
  # @since 0.5.0
46
55
  # @api private
47
56
  def database
48
- @database ||= opts.fetch(:database, parsed_uri.path[1..-1])
57
+ @database ||= parsed_uri.path[1..-1]
49
58
  end
50
59
 
51
60
  # Returns DB type
@@ -57,7 +66,14 @@ module Hanami
57
66
  # @since 0.5.0
58
67
  # @api private
59
68
  def database_type
60
- raw.database_type
69
+ case uri
70
+ when /sqlite/
71
+ :sqlite
72
+ when /postgres/
73
+ :postgres
74
+ when /mysql/
75
+ :mysql
76
+ end
61
77
  end
62
78
 
63
79
  # Returns user from DB connection
@@ -67,7 +83,7 @@ module Hanami
67
83
  # @since 0.5.0
68
84
  # @api private
69
85
  def user
70
- @user ||= opts.fetch(:user, parsed_opt('user'))
86
+ @user ||= parsed_opt('user')
71
87
  end
72
88
 
73
89
  # Returns user from DB connection
@@ -77,7 +93,7 @@ module Hanami
77
93
  # @since 0.5.0
78
94
  # @api private
79
95
  def password
80
- @password ||= opts.fetch(:password, parsed_opt('password'))
96
+ @password ||= parsed_opt('password')
81
97
  end
82
98
 
83
99
  # Returns DB connection URI directly from adapter
@@ -85,7 +101,7 @@ module Hanami
85
101
  # @since 0.5.0
86
102
  # @api private
87
103
  def uri
88
- raw.uri
104
+ @configuration.url
89
105
  end
90
106
 
91
107
  # Returns DB connection wihout specifying database name
@@ -93,7 +109,7 @@ module Hanami
93
109
  # @since 0.5.0
94
110
  # @api private
95
111
  def global_uri
96
- raw.uri.sub(parsed_uri.select(:path).first, '')
112
+ uri.sub(parsed_uri.select(:path).first, '')
97
113
  end
98
114
 
99
115
  # Returns a boolean telling if a DB connection is from JDBC or not
@@ -101,7 +117,7 @@ module Hanami
101
117
  # @since 0.5.0
102
118
  # @api private
103
119
  def jdbc?
104
- !raw.uri.scan('jdbc:').empty?
120
+ !uri.scan('jdbc:').empty?
105
121
  end
106
122
 
107
123
  # Returns database connection URI instance without JDBC namespace
@@ -109,7 +125,11 @@ module Hanami
109
125
  # @since 0.5.0
110
126
  # @api private
111
127
  def parsed_uri
112
- @uri ||= URI.parse(raw.uri.sub('jdbc:', ''))
128
+ @uri ||= URI.parse(uri.sub('jdbc:', ''))
129
+ end
130
+
131
+ def schema
132
+ configuration.schema
113
133
  end
114
134
 
115
135
  # Return the database table for the given name
@@ -122,6 +142,10 @@ module Hanami
122
142
 
123
143
  private
124
144
 
145
+ # @since 1.0.0.beta1
146
+ # @api private
147
+ attr_reader :configuration
148
+
125
149
  # Returns a value of a given query string param
126
150
  #
127
151
  # @param option [String] which option from database connection will be extracted from URI
@@ -131,14 +155,6 @@ module Hanami
131
155
  def parsed_opt(option)
132
156
  parsed_uri.to_s.match(/[\?|\&]#{ option }=(\w+)\&?/).to_a.last
133
157
  end
134
-
135
- # Fetch connection options from adapter
136
- #
137
- # @since 0.5.0
138
- # @api private
139
- def opts
140
- raw.opts
141
- end
142
158
  end
143
159
  end
144
160
  end
@@ -0,0 +1,33 @@
1
+ require 'hanami/logger'
2
+
3
+ module Hanami
4
+ module Model
5
+ class Migrator
6
+ # Automatic logger for migrations
7
+ #
8
+ # @since 1.0.0.beta1
9
+ # @api private
10
+ class Logger < Hanami::Logger
11
+ # Formatter for migrations logger
12
+ #
13
+ # @since 1.0.0.beta1
14
+ # @api private
15
+ class Formatter < Hanami::Logger::Formatter
16
+ private
17
+
18
+ # @since 1.0.0.beta1
19
+ # @api private
20
+ def _format(hash)
21
+ "[hanami] [#{hash.fetch(:severity)}] #{hash.fetch(:message)}\n"
22
+ end
23
+ end
24
+
25
+ # @since 1.0.0.beta1
26
+ # @api private
27
+ def initialize(stream)
28
+ super(nil, stream: stream, formatter: Formatter.new)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -10,16 +10,19 @@ module Hanami
10
10
  # @api private
11
11
  PASSWORD = 'MYSQL_PWD'.freeze
12
12
 
13
+ # @since 1.0.0.beta1
14
+ # @api private
15
+ DB_CREATION_ERROR = 'Database creation failed. If the database exists, ' \
16
+ 'then its console may be open. See this issue for more details: ' \
17
+ 'https://github.com/hanami/model/issues/250'.freeze
18
+
13
19
  # @since 0.4.0
14
20
  # @api private
15
- def create # rubocop:disable Metrics/MethodLength
21
+ def create
16
22
  new_connection(global: true).run %(CREATE DATABASE #{database};)
17
23
  rescue Sequel::DatabaseError => e
18
24
  message = if e.message.match(/database exists/) # rubocop:disable Performance/RedundantMatch
19
- "Database creation failed. If the database exists, \
20
- then its console may be open. See this issue for more details:\
21
- https://github.com/hanami/model/issues/250\
22
- "
25
+ DB_CREATION_ERROR
23
26
  else
24
27
  e.message
25
28
  end
@@ -22,17 +22,20 @@ module Hanami
22
22
  # @api private
23
23
  PASSWORD = 'PGPASSWORD'.freeze
24
24
 
25
+ # @since 1.0.0.beta1
26
+ # @api private
27
+ DB_CREATION_ERROR = 'createdb: database creation failed. If the database exists, ' \
28
+ 'then its console may be open. See this issue for more details: ' \
29
+ 'https://github.com/hanami/model/issues/250'.freeze
30
+
25
31
  # @since 0.4.0
26
32
  # @api private
27
- def create # rubocop:disable Metrics/MethodLength
33
+ def create
28
34
  set_environment_variables
29
35
 
30
36
  call_db_command('createdb') do |error_message|
31
37
  message = if error_message.match(/already exists/) # rubocop:disable Performance/RedundantMatch
32
- "createdb: database creation failed. If the database exists, \
33
- then its console may be open. See this issue for more details:\
34
- https://github.com/hanami/model/issues/250\
35
- "
38
+ DB_CREATION_ERROR
36
39
  else
37
40
  error_message
38
41
  end
@@ -29,7 +29,7 @@ module Hanami
29
29
  #
30
30
  # @since 0.4.0
31
31
  # @api private
32
- def initialize(connection, configuration)
32
+ def initialize(configuration)
33
33
  super
34
34
  extend Memory if memory?
35
35
  end
@@ -70,7 +70,7 @@ module Hanami
70
70
  # @api private
71
71
  def path
72
72
  root.join(
73
- @connection.uri.sub(/\A(jdbc:sqlite:|sqlite:\/\/)/, '')
73
+ @connection.uri.sub(/\A(jdbc:sqlite:\/\/|sqlite:\/\/)/, '')
74
74
  )
75
75
  end
76
76
 
@@ -15,7 +15,7 @@ module Hanami
15
15
  # @api private
16
16
  def initialize(relation, input)
17
17
  super
18
- @schema = relation.schema_hash
18
+ @schema = relation.input_schema
19
19
  end
20
20
 
21
21
  # Processes the input
@@ -63,7 +63,7 @@ module Hanami
63
63
  # @since 0.7.0
64
64
  # @api private
65
65
  def _touch(value, now)
66
- value[:updated_at] = now
66
+ value[:updated_at] ||= now
67
67
  value
68
68
  end
69
69
  end
@@ -79,7 +79,7 @@ module Hanami
79
79
  # @api private
80
80
  def _touch(value, now)
81
81
  super
82
- value[:created_at] = now
82
+ value[:created_at] ||= now
83
83
  value
84
84
  end
85
85
  end
@@ -145,6 +145,8 @@ module Hanami
145
145
  Error.register(ROM::SQL::UniqueConstraintError, UniqueConstraintViolationError)
146
146
  Error.register(ROM::SQL::CheckConstraintError, CheckConstraintViolationError)
147
147
  Error.register(ROM::SQL::ForeignKeyConstraintError, ForeignKeyConstraintViolationError)
148
+ Error.register(ROM::SQL::UnknownDBTypeError, UnknownDatabaseTypeError)
149
+ Error.register(ROM::SQL::MissingPrimaryKeyError, MissingPrimaryKeyError)
148
150
 
149
151
  Error.register(Java::JavaSql::SQLException, DatabaseError) if Utils.jruby?
150
152
  end
@@ -60,9 +60,10 @@ module Hanami
60
60
  #
61
61
  # @since 0.7.0
62
62
  # @api private
63
- def self.coercible(type)
64
- return type if type.constrained?
65
- MAPPING.fetch(type.with(meta: {}), type)
63
+ def self.coercible(attribute)
64
+ return attribute if attribute.constrained?
65
+ # TODO: figure out a better way of inferring coercions from schema types
66
+ MAPPING.fetch(attribute.type.with(meta: {}), attribute)
66
67
  end
67
68
 
68
69
  # Coercer for SQL associations target
@@ -3,6 +3,6 @@ module Hanami
3
3
  # Defines the version
4
4
  #
5
5
  # @since 0.1.0
6
- VERSION = '0.7.0'.freeze
6
+ VERSION = '1.0.0.beta1'.freeze
7
7
  end
8
8
  end
@@ -1,6 +1,7 @@
1
1
  require 'rom-repository'
2
2
  require 'hanami/model/entity_name'
3
3
  require 'hanami/model/relation_name'
4
+ require 'hanami/model/mapped_relation'
4
5
  require 'hanami/model/associations/dsl'
5
6
  require 'hanami/model/association'
6
7
  require 'hanami/utils/class'
@@ -107,19 +108,6 @@ module Hanami
107
108
  # @see http://martinfowler.com/eaaCatalog/repository.html
108
109
  # @see http://en.wikipedia.org/wiki/Dependency_inversion_principle
109
110
  class Repository < ROM::Repository::Root
110
- # Mapper name.
111
- #
112
- # With ROM mapping there is a link between the entity class and a generic
113
- # reference for it. Example: <tt>BookRepository</tt> references <tt>Book</tt>
114
- # as <tt>:entity</tt>.
115
- #
116
- # @since 0.7.0
117
- # @api private
118
- #
119
- # @see Hanami::Repository.inherited
120
- # @see Hanami::Repository.define_mapping
121
- MAPPER_NAME = :entity
122
-
123
111
  # Plugins for database commands
124
112
  #
125
113
  # @since 0.7.0
@@ -151,24 +139,33 @@ module Hanami
151
139
  #
152
140
  # @since 0.7.0
153
141
  # @api private
154
- def self.define_relation # rubocop:disable Metrics/MethodLength
142
+ #
143
+ # rubocop:disable Metrics/MethodLength
144
+ # rubocop:disable Metrics/AbcSize
145
+ def self.define_relation
155
146
  a = @associations
147
+ s = @schema
156
148
 
157
149
  configuration.relation(relation) do
158
- schema(infer: true) do
159
- associations(&a) unless a.nil?
150
+ if s.nil?
151
+ schema(infer: true) do
152
+ associations(&a) unless a.nil?
153
+ end
154
+ else
155
+ schema(&s)
160
156
  end
161
-
162
- # rubocop:disable Lint/NestedMethodDefinition
163
- def by_primary_key(id)
164
- where(primary_key => id)
165
- end
166
- # rubocop:enable Lint/NestedMethodDefinition
167
157
  end
168
158
 
169
159
  relations(relation)
170
160
  root(relation)
161
+ class_eval %{
162
+ def #{relation}
163
+ Hanami::Model::MappedRelation.new(@#{relation})
164
+ end
165
+ }
171
166
  end
167
+ # rubocop:enable Metrics/AbcSize
168
+ # rubocop:enable Metrics/MethodLength
172
169
 
173
170
  # Defines the ampping between a database table and an entity.
174
171
  #
@@ -186,7 +183,7 @@ module Hanami
186
183
 
187
184
  blk = lambda do |_|
188
185
  model e
189
- register_as MAPPER_NAME
186
+ register_as Model::MappedRelation.mapper_name
190
187
  instance_exec(&m) unless m.nil?
191
188
  end
192
189
 
@@ -225,6 +222,27 @@ module Hanami
225
222
  @associations = blk
226
223
  end
227
224
 
225
+ # Declare database schema
226
+ #
227
+ # NOTE: This should be used **only** when Hanami can't find a corresponding Ruby type for your column.
228
+ #
229
+ # @since 1.0.0.beta1
230
+ #
231
+ # @example
232
+ # # In this example `name` is a PostgreSQL Enum type that we want to treat like a string.
233
+ #
234
+ # class ColorRepository < Hanami::Repository
235
+ # schema do
236
+ # attribute :id, Hanami::Model::Sql::Types::Int
237
+ # attribute :name, Hanami::Model::Sql::Types::String
238
+ # attribute :created_at, Hanami::Model::Sql::Types::DateTime
239
+ # attribute :updated_at, Hanami::Model::Sql::Types::DateTime
240
+ # end
241
+ # end
242
+ def self.schema(&blk)
243
+ @schema = blk
244
+ end
245
+
228
246
  # Declare mapping between database columns and entity's attributes
229
247
  #
230
248
  # NOTE: This should be used **only** when there is a name mismatch (eg. in legacy databases).
@@ -268,7 +286,7 @@ module Hanami
268
286
  class_attribute :relation
269
287
  self.relation = Model::RelationName.new(name)
270
288
 
271
- commands :create, update: :by_primary_key, delete: :by_primary_key, mapper: MAPPER_NAME, use: COMMAND_PLUGINS
289
+ commands :create, update: :by_pk, delete: :by_pk, mapper: Model::MappedRelation.mapper_name, use: COMMAND_PLUGINS
272
290
  prepend Commands
273
291
  end
274
292
 
@@ -364,6 +382,9 @@ module Hanami
364
382
  #
365
383
  # @return [Hanami::Entity,NilClass] the entity, if found
366
384
  #
385
+ # @raise [Hanami::Model::MissingPrimaryKeyError] if the table doesn't
386
+ # define a primary key
387
+ #
367
388
  # @since 0.7.0
368
389
  #
369
390
  # @example
@@ -372,7 +393,9 @@ module Hanami
372
393
  #
373
394
  # user = repository.find(user.id)
374
395
  def find(id)
375
- root.by_primary_key(id).as(:entity).one
396
+ root.by_pk(id).as(:entity).one
397
+ rescue => e
398
+ raise Hanami::Model::Error.for(e)
376
399
  end
377
400
 
378
401
  # Return all the records for the relation
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hanami-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 1.0.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luca Guidi
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2016-11-15 00:00:00.000000000 Z
13
+ date: 2017-02-14 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: hanami-utils
@@ -18,42 +18,42 @@ dependencies:
18
18
  requirements:
19
19
  - - "~>"
20
20
  - !ruby/object:Gem::Version
21
- version: '0.8'
21
+ version: 1.0.0.beta
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
26
  - - "~>"
27
27
  - !ruby/object:Gem::Version
28
- version: '0.8'
28
+ version: 1.0.0.beta
29
29
  - !ruby/object:Gem::Dependency
30
30
  name: rom-sql
31
31
  requirement: !ruby/object:Gem::Requirement
32
32
  requirements:
33
33
  - - "~>"
34
34
  - !ruby/object:Gem::Version
35
- version: '0.9'
35
+ version: '1.0'
36
36
  type: :runtime
37
37
  prerelease: false
38
38
  version_requirements: !ruby/object:Gem::Requirement
39
39
  requirements:
40
40
  - - "~>"
41
41
  - !ruby/object:Gem::Version
42
- version: '0.9'
42
+ version: '1.0'
43
43
  - !ruby/object:Gem::Dependency
44
44
  name: rom-repository
45
45
  requirement: !ruby/object:Gem::Requirement
46
46
  requirements:
47
47
  - - "~>"
48
48
  - !ruby/object:Gem::Version
49
- version: '0.3'
49
+ version: '1.0'
50
50
  type: :runtime
51
51
  prerelease: false
52
52
  version_requirements: !ruby/object:Gem::Requirement
53
53
  requirements:
54
54
  - - "~>"
55
55
  - !ruby/object:Gem::Version
56
- version: '0.3'
56
+ version: '1.0'
57
57
  - !ruby/object:Gem::Dependency
58
58
  name: dry-types
59
59
  requirement: !ruby/object:Gem::Requirement
@@ -149,11 +149,13 @@ files:
149
149
  - lib/hanami/model/configurator.rb
150
150
  - lib/hanami/model/entity_name.rb
151
151
  - lib/hanami/model/error.rb
152
+ - lib/hanami/model/mapped_relation.rb
152
153
  - lib/hanami/model/mapping.rb
153
154
  - lib/hanami/model/migration.rb
154
155
  - lib/hanami/model/migrator.rb
155
156
  - lib/hanami/model/migrator/adapter.rb
156
157
  - lib/hanami/model/migrator/connection.rb
158
+ - lib/hanami/model/migrator/logger.rb
157
159
  - lib/hanami/model/migrator/mysql_adapter.rb
158
160
  - lib/hanami/model/migrator/postgres_adapter.rb
159
161
  - lib/hanami/model/migrator/sqlite_adapter.rb
@@ -189,12 +191,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
189
191
  version: 2.3.0
190
192
  required_rubygems_version: !ruby/object:Gem::Requirement
191
193
  requirements:
192
- - - ">="
194
+ - - ">"
193
195
  - !ruby/object:Gem::Version
194
- version: '0'
196
+ version: 1.3.1
195
197
  requirements: []
196
198
  rubyforge_project:
197
- rubygems_version: 2.6.4
199
+ rubygems_version: 2.6.8
198
200
  signing_key:
199
201
  specification_version: 4
200
202
  summary: A persistence layer for Hanami