hanami-model 1.0.1 → 1.0.2

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: bb1fca18a6f84f606883ffab656334c1b8e619f6
4
- data.tar.gz: 8d2884fbd3bfda01dc6390b4271280cfffe1abc2
3
+ metadata.gz: b42e3225f01dfdc11db772e01e9380930378b34b
4
+ data.tar.gz: 61c681f2b8dacd1867ac9b929e84f2fc2d9da120
5
5
  SHA512:
6
- metadata.gz: 4af0a977d17c5cab779ba6b7fefc91b5e94ffa927324439ed99d45fd468000289f564f151c48e1926e2e603052967077517e055735dbe719152a021ccb2c7282
7
- data.tar.gz: f4c30f92ab810dde2c9678d8352bec0787e2c446235e70a078e7a419913bf2fc59d8d0fdcd30727be296085b74f7dc89f300bb57cca463437cf94805ce2b2800
6
+ metadata.gz: c7c31f943c0af575a6b1197c87d2ce0bcf1b1ccf3e42dab6869c85580e79f5f316c62d7c52093a8b96b4fb5ace02e237723fd3fa115398247e68c409871cb592
7
+ data.tar.gz: f6b396a6f8e57b35d9a213f9d49c45610a3dc3825fc8233e83721af811bdf411b270e6d1ce8420d745d91d725ba3ca826ffa1b6dca02e4976c44de79dc74a6e8
@@ -1,6 +1,13 @@
1
1
  # Hanami::Model
2
2
  A persistence layer for Hanami
3
3
 
4
+ ## v1.0.2 - 2017-08-04
5
+ ### Fixed
6
+ - [Maurizio De Magnis] URI escape for Postgres password
7
+ - [Marion Duprey] Ensure repository to generate timestamps values even when only one between `created_at` and `updated_at` is present
8
+ - [Paweł Świątkowski] Make Postgres JSON(B) to work with Ruby arrays
9
+ - [Luca Guidi] Don't remove migrations when running `Hanami::Model::Migrator#apply` fails to dump the database
10
+
4
11
  ## v1.0.1 - 2017-06-23
5
12
  ### Fixed
6
13
  - [Kai Kuchenbecker & Marcello Rocha & Luca Guidi] Ensure `Hanami::Entity#initialize` to not serialize (into `Hash`) other entities passed as an argument
@@ -22,8 +22,8 @@ Gem::Specification.new do |spec|
22
22
 
23
23
  spec.add_runtime_dependency 'hanami-utils', '~> 1.0'
24
24
  spec.add_runtime_dependency 'rom-sql', '~> 1.3'
25
- spec.add_runtime_dependency 'rom-repository', '~> 1.3'
26
- spec.add_runtime_dependency 'dry-types', '~> 0.10'
25
+ spec.add_runtime_dependency 'rom-repository', '~> 1.4'
26
+ spec.add_runtime_dependency 'dry-types', '~> 0.11'
27
27
  spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
28
28
 
29
29
  spec.add_development_dependency 'bundler', '~> 1.6'
@@ -1,5 +1,6 @@
1
1
  require 'uri'
2
2
  require 'shellwords'
3
+ require 'open3'
3
4
 
4
5
  module Hanami
5
6
  module Model
@@ -176,6 +177,15 @@ module Hanami
176
177
  def escape(string)
177
178
  Shellwords.escape(string) unless string.nil?
178
179
  end
180
+
181
+ # @since 1.0.2
182
+ # @api private
183
+ def execute(command, env: {}, error: ->(err) { raise MigrationError.new(err) })
184
+ Open3.popen3(env, command) do |_, stdout, stderr, wait_thr|
185
+ error.call(stderr.read) unless wait_thr.value.success?
186
+ yield stdout if block_given?
187
+ end
188
+ end
179
189
  end
180
190
  end
181
191
  end
@@ -47,7 +47,6 @@ module Hanami
47
47
  # @since 0.4.0
48
48
  # @api private
49
49
  def dump
50
- set_environment_variables
51
50
  dump_structure
52
51
  dump_migrations_data
53
52
  end
@@ -55,18 +54,11 @@ module Hanami
55
54
  # @since 0.4.0
56
55
  # @api private
57
56
  def load
58
- set_environment_variables
59
57
  load_structure
60
58
  end
61
59
 
62
60
  private
63
61
 
64
- # @since 0.7.0
65
- # @api private
66
- def set_environment_variables
67
- ENV[PASSWORD] = password unless password.nil?
68
- end
69
-
70
62
  # @since 0.7.0
71
63
  # @api private
72
64
  def password
@@ -76,19 +68,19 @@ module Hanami
76
68
  # @since 0.4.0
77
69
  # @api private
78
70
  def dump_structure
79
- system "mysqldump --user=#{username} --no-data --skip-comments --ignore-table=#{database}.#{migrations_table} #{database} > #{schema}"
71
+ execute "mysqldump --host=#{host} --port=#{port} --user=#{username} --no-data --skip-comments --ignore-table=#{database}.#{migrations_table} #{database} > #{schema}", env: { PASSWORD => password }
80
72
  end
81
73
 
82
74
  # @since 0.4.0
83
75
  # @api private
84
76
  def load_structure
85
- system "mysql --user=#{username} #{database} < #{escape(schema)}" if schema.exist?
77
+ execute("mysql --host=#{host} --port=#{port} --user=#{username} #{database} < #{escape(schema)}", env: { PASSWORD => password }) if schema.exist?
86
78
  end
87
79
 
88
80
  # @since 0.4.0
89
81
  # @api private
90
82
  def dump_migrations_data
91
- system "mysqldump --user=#{username} --skip-comments #{database} #{migrations_table} >> #{schema}"
83
+ execute "mysqldump --host=#{host} --port=#{port} --user=#{username} --skip-comments #{database} #{migrations_table} >> #{schema}", env: { PASSWORD => password }
92
84
  end
93
85
  end
94
86
  end
@@ -89,19 +89,20 @@ module Hanami
89
89
  # @since 0.4.0
90
90
  # @api private
91
91
  def dump_structure
92
- system "pg_dump -s -x -O -T #{migrations_table} -f #{escape(schema)} #{database}"
92
+ execute "pg_dump -s -x -O -T #{migrations_table} -f #{escape(schema)} #{database}"
93
93
  end
94
94
 
95
95
  # @since 0.4.0
96
96
  # @api private
97
97
  def load_structure
98
- system "psql -X -q -f #{escape(schema)} #{database}" if schema.exist?
98
+ execute "psql -X -q -f #{escape(schema)} #{database}" if schema.exist?
99
99
  end
100
100
 
101
101
  # @since 0.4.0
102
102
  # @api private
103
103
  def dump_migrations_data
104
- system "pg_dump -t #{migrations_table} #{database} >> #{escape(schema)}"
104
+ error = ->(err) { raise MigrationError.new(err) unless err =~ /no matching tables/i }
105
+ execute "pg_dump -t #{migrations_table} #{database} >> #{escape(schema)}", error: error
105
106
  end
106
107
 
107
108
  # @since 0.5.1
@@ -1,5 +1,6 @@
1
1
  require 'pathname'
2
2
  require 'hanami/utils'
3
+ require 'English'
3
4
 
4
5
  module Hanami
5
6
  module Model
@@ -91,20 +92,36 @@ module Hanami
91
92
  # @since 0.4.0
92
93
  # @api private
93
94
  def dump_structure
94
- system "sqlite3 #{escape(path)} .schema > #{escape(schema)}"
95
+ execute "sqlite3 #{escape(path)} .schema > #{escape(schema)}"
95
96
  end
96
97
 
97
98
  # @since 0.4.0
98
99
  # @api private
99
100
  def load_structure
100
- system "sqlite3 #{escape(path)} < #{escape(schema)}" if schema.exist?
101
+ execute "sqlite3 #{escape(path)} < #{escape(schema)}" if schema.exist?
101
102
  end
102
103
 
103
104
  # @since 0.4.0
104
105
  # @api private
106
+ #
107
+ # rubocop:disable Metrics/AbcSize
108
+ # rubocop:disable Metrics/MethodLength
105
109
  def dump_migrations_data
106
- system %(sqlite3 #{escape(path)} .dump | grep '^INSERT INTO "#{migrations_table}"' >> #{escape(schema)})
110
+ execute "sqlite3 #{escape(path)} .dump" do |stdout|
111
+ begin
112
+ contents = stdout.read.split($INPUT_RECORD_SEPARATOR)
113
+ contents = contents.grep(/^INSERT INTO "#{migrations_table}"/)
114
+
115
+ ::File.open(schema, ::File::CREAT | ::File::BINARY | ::File::WRONLY | ::File::APPEND) do |file|
116
+ file.write(contents.join($INPUT_RECORD_SEPARATOR))
117
+ end
118
+ rescue => exception
119
+ raise MigrationError.new(exception.message)
120
+ end
121
+ end
107
122
  end
123
+ # rubocop:enable Metrics/MethodLength
124
+ # rubocop:enable Metrics/AbcSize
108
125
  end
109
126
  end
110
127
  end
@@ -23,8 +23,7 @@ module Hanami
23
23
  # @api private
24
24
  def initialize(relation, input)
25
25
  super
26
- columns = relation.columns.sort
27
- @timestamps = (columns & TIMESTAMPS) == TIMESTAMPS
26
+ @timestamps = relation.columns & TIMESTAMPS
28
27
  end
29
28
 
30
29
  # Processes the input
@@ -49,7 +48,7 @@ module Hanami
49
48
  # @since 0.7.0
50
49
  # @api private
51
50
  def timestamps?
52
- @timestamps
51
+ !@timestamps.empty?
53
52
  end
54
53
  end
55
54
 
@@ -63,7 +62,7 @@ module Hanami
63
62
  # @since 0.7.0
64
63
  # @api private
65
64
  def _touch(value, now)
66
- value[:updated_at] ||= now
65
+ value[:updated_at] ||= now if @timestamps.include?(:updated_at)
67
66
  value
68
67
  end
69
68
  end
@@ -79,7 +78,7 @@ module Hanami
79
78
  # @api private
80
79
  def _touch(value, now)
81
80
  super
82
- value[:created_at] ||= now
81
+ value[:created_at] ||= now if @timestamps.include?(:created_at)
83
82
  value
84
83
  end
85
84
  end
@@ -1,4 +1,5 @@
1
1
  require_relative 'abstract'
2
+ require 'cgi'
2
3
 
3
4
  module Hanami
4
5
  module Model
@@ -59,7 +60,7 @@ module Hanami
59
60
  # @since 0.7.0
60
61
  # @api private
61
62
  def configure_password
62
- ENV[PASSWORD] = @uri.password unless @uri.password.nil?
63
+ ENV[PASSWORD] = CGI.unescape(@uri.password) unless @uri.password.nil?
63
64
  end
64
65
  end
65
66
  end
@@ -31,6 +31,8 @@ module Hanami
31
31
  Array = Types::Strict::Nil | Types::Array.constructor(Coercions.method(:array))
32
32
  Hash = Types::Strict::Nil | Types::Hash.constructor(Coercions.method(:hash))
33
33
 
34
+ PG_JSON = Types::Strict::Nil | Types::Any.constructor(Coercions.method(:pg_json))
35
+
34
36
  # @since 0.7.0
35
37
  # @api private
36
38
  MAPPING = {
@@ -72,14 +74,23 @@ module Hanami
72
74
  #
73
75
  # MAPPING.fetch(unwrapped.pristine, attribute)
74
76
  MAPPING.fetch(unwrapped.pristine) do
75
- if defined?(ROM::SQL::Types::PG::JSONB) && unwrapped.pristine == ROM::SQL::Types::PG::JSONB
76
- Schema::Hash
77
+ if pg_json?(unwrapped.pristine)
78
+ Schema::PG_JSON
77
79
  else
78
80
  attribute
79
81
  end
80
82
  end
81
83
  end
82
84
 
85
+ # @since 1.0.2
86
+ # @api private
87
+ def self.pg_json?(pristine)
88
+ (defined?(ROM::SQL::Types::PG::JSONB) && pristine == ROM::SQL::Types::PG::JSONB) ||
89
+ (defined?(ROM::SQL::Types::PG::JSON) && pristine == ROM::SQL::Types::PG::JSON)
90
+ end
91
+
92
+ private_class_method :pg_json?
93
+
83
94
  # Coercer for SQL associations target
84
95
  #
85
96
  # @since 0.7.0
@@ -11,6 +11,7 @@ module Hanami
11
11
  # @since 0.7.0
12
12
  # @api private
13
13
  #
14
+ # rubocop:disable Metrics/ModuleLength
14
15
  # rubocop:disable Metrics/MethodLength
15
16
  module Coercions
16
17
  # Coerces given argument into Integer
@@ -192,8 +193,30 @@ module Hanami
192
193
  raise ArgumentError.new("invalid value for Hash(): #{arg.inspect}")
193
194
  end
194
195
  end
196
+
197
+ # Coerces given argument to appropriate Postgres JSON(B) type, i.e. Hash or Array
198
+ #
199
+ # @param arg [Object] the object to coerce
200
+ #
201
+ # @return [Hash, Array] the result of the coercion
202
+ #
203
+ # @raise [ArgumentError] if the coercion fails
204
+ #
205
+ # @since 1.0.2
206
+ # @api private
207
+ def self.pg_json(arg)
208
+ case arg
209
+ when ->(a) { a.respond_to?(:to_hash) }
210
+ hash(arg)
211
+ when ->(a) { a.respond_to?(:to_a) }
212
+ array(arg)
213
+ else
214
+ raise ArgumentError.new("invalid value for PG_JSON(): #{arg.inspect}")
215
+ end
216
+ end
195
217
  end
196
218
  # rubocop:enable Metrics/MethodLength
219
+ # rubocop:enable Metrics/ModuleLength
197
220
  end
198
221
  end
199
222
  end
@@ -3,6 +3,6 @@ module Hanami
3
3
  # Defines the version
4
4
  #
5
5
  # @since 0.1.0
6
- VERSION = '1.0.1'.freeze
6
+ VERSION = '1.0.2'.freeze
7
7
  end
8
8
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hanami-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.2
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-06-23 00:00:00.000000000 Z
11
+ date: 2017-08-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hanami-utils
@@ -44,28 +44,28 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '1.3'
47
+ version: '1.4'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '1.3'
54
+ version: '1.4'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: dry-types
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0.10'
61
+ version: '0.11'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0.10'
68
+ version: '0.11'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: concurrent-ruby
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -192,7 +192,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
192
192
  version: '0'
193
193
  requirements: []
194
194
  rubyforge_project:
195
- rubygems_version: 2.6.12
195
+ rubygems_version: 2.6.11
196
196
  signing_key:
197
197
  specification_version: 4
198
198
  summary: A persistence layer for Hanami