hanami-model 0.7.0 → 1.0.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: 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