hanami-model 0.6.1 → 0.7.0

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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -0
  3. data/README.md +54 -420
  4. data/hanami-model.gemspec +9 -6
  5. data/lib/hanami/entity.rb +107 -191
  6. data/lib/hanami/entity/schema.rb +236 -0
  7. data/lib/hanami/model.rb +52 -138
  8. data/lib/hanami/model/association.rb +37 -0
  9. data/lib/hanami/model/associations/belongs_to.rb +19 -0
  10. data/lib/hanami/model/associations/dsl.rb +29 -0
  11. data/lib/hanami/model/associations/has_many.rb +200 -0
  12. data/lib/hanami/model/configuration.rb +52 -224
  13. data/lib/hanami/model/configurator.rb +62 -0
  14. data/lib/hanami/model/entity_name.rb +35 -0
  15. data/lib/hanami/model/error.rb +37 -24
  16. data/lib/hanami/model/mapping.rb +29 -35
  17. data/lib/hanami/model/migration.rb +31 -0
  18. data/lib/hanami/model/migrator.rb +111 -88
  19. data/lib/hanami/model/migrator/adapter.rb +39 -16
  20. data/lib/hanami/model/migrator/connection.rb +23 -11
  21. data/lib/hanami/model/migrator/mysql_adapter.rb +38 -17
  22. data/lib/hanami/model/migrator/postgres_adapter.rb +20 -19
  23. data/lib/hanami/model/migrator/sqlite_adapter.rb +9 -8
  24. data/lib/hanami/model/plugins.rb +25 -0
  25. data/lib/hanami/model/plugins/mapping.rb +55 -0
  26. data/lib/hanami/model/plugins/schema.rb +55 -0
  27. data/lib/hanami/model/plugins/timestamps.rb +118 -0
  28. data/lib/hanami/model/relation_name.rb +24 -0
  29. data/lib/hanami/model/sql.rb +161 -0
  30. data/lib/hanami/model/sql/console.rb +41 -0
  31. data/lib/hanami/model/sql/consoles/abstract.rb +33 -0
  32. data/lib/hanami/model/sql/consoles/mysql.rb +63 -0
  33. data/lib/hanami/model/sql/consoles/postgresql.rb +68 -0
  34. data/lib/hanami/model/sql/consoles/sqlite.rb +46 -0
  35. data/lib/hanami/model/sql/entity/schema.rb +125 -0
  36. data/lib/hanami/model/sql/types.rb +95 -0
  37. data/lib/hanami/model/sql/types/schema/coercions.rb +198 -0
  38. data/lib/hanami/model/types.rb +99 -0
  39. data/lib/hanami/model/version.rb +1 -1
  40. data/lib/hanami/repository.rb +287 -723
  41. metadata +77 -40
  42. data/EXAMPLE.md +0 -213
  43. data/lib/hanami/entity/dirty_tracking.rb +0 -74
  44. data/lib/hanami/model/adapters/abstract.rb +0 -281
  45. data/lib/hanami/model/adapters/file_system_adapter.rb +0 -288
  46. data/lib/hanami/model/adapters/implementation.rb +0 -111
  47. data/lib/hanami/model/adapters/memory/collection.rb +0 -132
  48. data/lib/hanami/model/adapters/memory/command.rb +0 -113
  49. data/lib/hanami/model/adapters/memory/query.rb +0 -653
  50. data/lib/hanami/model/adapters/memory_adapter.rb +0 -179
  51. data/lib/hanami/model/adapters/null_adapter.rb +0 -24
  52. data/lib/hanami/model/adapters/sql/collection.rb +0 -287
  53. data/lib/hanami/model/adapters/sql/command.rb +0 -88
  54. data/lib/hanami/model/adapters/sql/console.rb +0 -33
  55. data/lib/hanami/model/adapters/sql/consoles/mysql.rb +0 -49
  56. data/lib/hanami/model/adapters/sql/consoles/postgresql.rb +0 -48
  57. data/lib/hanami/model/adapters/sql/consoles/sqlite.rb +0 -26
  58. data/lib/hanami/model/adapters/sql/query.rb +0 -788
  59. data/lib/hanami/model/adapters/sql_adapter.rb +0 -296
  60. data/lib/hanami/model/coercer.rb +0 -74
  61. data/lib/hanami/model/config/adapter.rb +0 -116
  62. data/lib/hanami/model/config/mapper.rb +0 -45
  63. data/lib/hanami/model/mapper.rb +0 -124
  64. data/lib/hanami/model/mapping/attribute.rb +0 -85
  65. data/lib/hanami/model/mapping/coercers.rb +0 -314
  66. data/lib/hanami/model/mapping/collection.rb +0 -490
  67. data/lib/hanami/model/mapping/collection_coercer.rb +0 -79
@@ -0,0 +1,118 @@
1
+ module Hanami
2
+ module Model
3
+ module Plugins
4
+ # Automatically set/update timestamp columns for create/update commands
5
+ #
6
+ # @since 0.7.0
7
+ # @api private
8
+ module Timestamps
9
+ # Takes the input and applies the timestamp transformation.
10
+ # This is an "abstract class", please look at the subclasses for
11
+ # specific behaviors.
12
+ #
13
+ # @since 0.7.0
14
+ # @api private
15
+ class InputWithTimestamp < WrappingInput
16
+ # Conventional timestamp names
17
+ #
18
+ # @since 0.7.0
19
+ # @api private
20
+ TIMESTAMPS = [:created_at, :updated_at].freeze
21
+
22
+ # @since 0.7.0
23
+ # @api private
24
+ def initialize(relation, input)
25
+ super
26
+ columns = relation.columns.sort
27
+ @timestamps = (columns & TIMESTAMPS) == TIMESTAMPS
28
+ end
29
+
30
+ # Processes the input
31
+ #
32
+ # @since 0.7.0
33
+ # @api private
34
+ def [](value)
35
+ return value unless timestamps?
36
+ _touch(@input[value], Time.now)
37
+ end
38
+
39
+ protected
40
+
41
+ # @since 0.7.0
42
+ # @api private
43
+ def _touch(_value)
44
+ raise NotImplementedError
45
+ end
46
+
47
+ private
48
+
49
+ # @since 0.7.0
50
+ # @api private
51
+ def timestamps?
52
+ @timestamps
53
+ end
54
+ end
55
+
56
+ # Updates <tt>updated_at</tt> timestamp for update commands
57
+ #
58
+ # @since 0.7.0
59
+ # @api private
60
+ class InputWithUpdateTimestamp < InputWithTimestamp
61
+ protected
62
+
63
+ # @since 0.7.0
64
+ # @api private
65
+ def _touch(value, now)
66
+ value[:updated_at] = now
67
+ value
68
+ end
69
+ end
70
+
71
+ # Sets <tt>created_at</tt> and <tt>updated_at</tt> timestamps for create commands
72
+ #
73
+ # @since 0.7.0
74
+ # @api private
75
+ class InputWithCreateTimestamp < InputWithUpdateTimestamp
76
+ protected
77
+
78
+ # @since 0.7.0
79
+ # @api private
80
+ def _touch(value, now)
81
+ super
82
+ value[:created_at] = now
83
+ value
84
+ end
85
+ end
86
+
87
+ # Class interface
88
+ #
89
+ # @since 0.7.0
90
+ # @api private
91
+ module ClassMethods
92
+ # Build an input processor according to the current command (create or update).
93
+ #
94
+ # @since 0.7.0
95
+ # @api private
96
+ def build(relation, options = {})
97
+ plugin = if self < ROM::Commands::Create
98
+ InputWithCreateTimestamp
99
+ else
100
+ InputWithUpdateTimestamp
101
+ end
102
+
103
+ input(plugin.new(relation, input))
104
+ super(relation, options.merge(input: input))
105
+ end
106
+ end
107
+
108
+ # @since 0.7.0
109
+ # @api private
110
+ def self.included(klass)
111
+ super
112
+
113
+ klass.extend ClassMethods
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,24 @@
1
+ require_relative 'entity_name'
2
+ require 'hanami/utils/string'
3
+
4
+ module Hanami
5
+ module Model
6
+ # Conventional name for relations.
7
+ #
8
+ # Given a repository named <tt>SourceFileRepository</tt>, the associated
9
+ # relation will be <tt>:source_files</tt>.
10
+ #
11
+ # @since 0.7.0
12
+ # @api private
13
+ class RelationName < EntityName
14
+ # @param name [Class,String] the class or its name
15
+ # @return [Symbol] the relation name
16
+ #
17
+ # @since 0.7.0
18
+ # @api private
19
+ def self.new(name)
20
+ Utils::String.new(super).underscore.pluralize.to_sym
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,161 @@
1
+ require 'rom-sql'
2
+ require 'hanami/utils'
3
+
4
+ module Hanami
5
+ # Hanami::Model migrations
6
+ module Model
7
+ require 'hanami/model/error'
8
+ require 'hanami/model/association'
9
+ require 'hanami/model/migration'
10
+
11
+ # Define a migration
12
+ #
13
+ # It must define an up/down strategy to write schema changes (up) and to
14
+ # rollback them (down).
15
+ #
16
+ # We can use <tt>up</tt> and <tt>down</tt> blocks for custom strategies, or
17
+ # only one <tt>change</tt> block that automatically implements "down" strategy.
18
+ #
19
+ # @param blk [Proc] a block that defines up/down or change database migration
20
+ #
21
+ # @since 0.4.0
22
+ #
23
+ # @example Use up/down blocks
24
+ # Hanami::Model.migration do
25
+ # up do
26
+ # create_table :books do
27
+ # primary_key :id
28
+ # column :book, String
29
+ # end
30
+ # end
31
+ #
32
+ # down do
33
+ # drop_table :books
34
+ # end
35
+ # end
36
+ #
37
+ # @example Use change block
38
+ # Hanami::Model.migration do
39
+ # change do
40
+ # create_table :books do
41
+ # primary_key :id
42
+ # column :book, String
43
+ # end
44
+ # end
45
+ #
46
+ # # DOWN strategy is automatically generated
47
+ # end
48
+ def self.migration(&blk)
49
+ Migration.new(configuration.gateways[:default], &blk)
50
+ end
51
+
52
+ # SQL adapter
53
+ #
54
+ # @since 0.7.0
55
+ module Sql
56
+ require 'hanami/model/sql/types'
57
+ require 'hanami/model/sql/entity/schema'
58
+
59
+ # Returns a SQL fragment that references a database function by the given name
60
+ # This is useful for database migrations
61
+ #
62
+ # @param name [String,Symbol] the function name
63
+ # @return [String] the SQL fragment
64
+ #
65
+ # @since 0.7.0
66
+ #
67
+ # @example
68
+ # Hanami::Model.migration do
69
+ # up do
70
+ # execute 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'
71
+ #
72
+ # create_table :source_files do
73
+ # column :id, 'uuid', primary_key: true, default: Hanami::Model::Sql.function(:uuid_generate_v4)
74
+ # # ...
75
+ # end
76
+ # end
77
+ #
78
+ # down do
79
+ # drop_table :source_files
80
+ # execute 'DROP EXTENSION "uuid-ossp"'
81
+ # end
82
+ # end
83
+ def self.function(name)
84
+ Sequel.function(name)
85
+ end
86
+
87
+ # Returns a literal SQL fragment for the given SQL fragment.
88
+ # This is useful for database migrations
89
+ #
90
+ # @param string [String] the SQL fragment
91
+ # @return [String] the literal SQL fragment
92
+ #
93
+ # @since 0.7.0
94
+ #
95
+ # @example
96
+ # Hanami::Model.migration do
97
+ # up do
98
+ # execute %{
99
+ # CREATE TYPE inventory_item AS (
100
+ # name text,
101
+ # supplier_id integer,
102
+ # price numeric
103
+ # );
104
+ # }
105
+ #
106
+ # create_table :items do
107
+ # column :item, 'inventory_item', default: Hanami::Model::Sql.literal("ROW('fuzzy dice', 42, 1.99)")
108
+ # # ...
109
+ # end
110
+ # end
111
+ #
112
+ # down do
113
+ # drop_table :itmes
114
+ # execute 'DROP TYPE inventory_item'
115
+ # end
116
+ # end
117
+ def self.literal(string)
118
+ Sequel.lit(string)
119
+ end
120
+
121
+ # Returns SQL fragment for ascending order for the given column
122
+ #
123
+ # @param column [Symbol] the column name
124
+ # @return [String] the SQL fragment
125
+ #
126
+ # @since 0.7.0
127
+ def self.asc(column)
128
+ Sequel.asc(column)
129
+ end
130
+
131
+ # Returns SQL fragment for descending order for the given column
132
+ #
133
+ # @param column [Symbol] the column name
134
+ # @return [String] the SQL fragment
135
+ #
136
+ # @since 0.7.0
137
+ def self.desc(column)
138
+ Sequel.desc(column)
139
+ end
140
+ end
141
+
142
+ Error.register(ROM::SQL::DatabaseError, DatabaseError)
143
+ Error.register(ROM::SQL::ConstraintError, ConstraintViolationError)
144
+ Error.register(ROM::SQL::NotNullConstraintError, NotNullConstraintViolationError)
145
+ Error.register(ROM::SQL::UniqueConstraintError, UniqueConstraintViolationError)
146
+ Error.register(ROM::SQL::CheckConstraintError, CheckConstraintViolationError)
147
+ Error.register(ROM::SQL::ForeignKeyConstraintError, ForeignKeyConstraintViolationError)
148
+
149
+ Error.register(Java::JavaSql::SQLException, DatabaseError) if Utils.jruby?
150
+ end
151
+ end
152
+
153
+ Sequel.default_timezone = :utc
154
+
155
+ ROM.plugins do
156
+ adapter :sql do
157
+ register :mapping, Hanami::Model::Plugins::Mapping, type: :command
158
+ register :schema, Hanami::Model::Plugins::Schema, type: :command
159
+ register :timestamps, Hanami::Model::Plugins::Timestamps, type: :command
160
+ end
161
+ end
@@ -0,0 +1,41 @@
1
+ require 'uri'
2
+
3
+ module Hanami
4
+ module Model
5
+ module Sql
6
+ # SQL console
7
+ #
8
+ # @since 0.7.0
9
+ # @api private
10
+ class Console
11
+ extend Forwardable
12
+
13
+ def_delegator :console, :connection_string
14
+
15
+ # @since 0.7.0
16
+ # @api private
17
+ def initialize(uri)
18
+ @uri = URI.parse(uri)
19
+ end
20
+
21
+ private
22
+
23
+ # @since 0.7.0
24
+ # @api private
25
+ def console # rubocop:disable Metrics/MethodLength
26
+ case @uri.scheme
27
+ when 'sqlite'
28
+ require 'hanami/model/sql/consoles/sqlite'
29
+ Sql::Consoles::Sqlite.new(@uri)
30
+ when 'postgres'
31
+ require 'hanami/model/sql/consoles/postgresql'
32
+ Sql::Consoles::Postgresql.new(@uri)
33
+ when 'mysql', 'mysql2'
34
+ require 'hanami/model/sql/consoles/mysql'
35
+ Sql::Consoles::Mysql.new(@uri)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,33 @@
1
+ module Hanami
2
+ module Model
3
+ module Sql
4
+ module Consoles
5
+ # Abstract adapter
6
+ #
7
+ # @since 0.7.0
8
+ # @api private
9
+ class Abstract
10
+ # @since 0.7.0
11
+ # @api private
12
+ def initialize(uri)
13
+ @uri = uri
14
+ end
15
+
16
+ private
17
+
18
+ # @since 0.7.0
19
+ # @api private
20
+ def database_name
21
+ @uri.path.sub(/^\//, '')
22
+ end
23
+
24
+ # @since 0.7.0
25
+ # @api private
26
+ def concat(*tokens)
27
+ tokens.join
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,63 @@
1
+ require_relative 'abstract'
2
+
3
+ module Hanami
4
+ module Model
5
+ module Sql
6
+ module Consoles
7
+ # MySQL adapter
8
+ #
9
+ # @since 0.7.0
10
+ # @api private
11
+ class Mysql < Abstract
12
+ # @since 0.7.0
13
+ # @api private
14
+ COMMAND = 'mysql'.freeze
15
+
16
+ # @since 0.7.0
17
+ # @api private
18
+ def connection_string
19
+ concat(command, host, database, port, username, password)
20
+ end
21
+
22
+ private
23
+
24
+ # @since 0.7.0
25
+ # @api private
26
+ def command
27
+ COMMAND
28
+ end
29
+
30
+ # @since 0.7.0
31
+ # @api private
32
+ def host
33
+ " -h #{@uri.host}"
34
+ end
35
+
36
+ # @since 0.7.0
37
+ # @api private
38
+ def database
39
+ " -D #{database_name}"
40
+ end
41
+
42
+ # @since 0.7.0
43
+ # @api private
44
+ def port
45
+ " -P #{@uri.port}" unless @uri.port.nil?
46
+ end
47
+
48
+ # @since 0.7.0
49
+ # @api private
50
+ def username
51
+ " -u #{@uri.user}" unless @uri.user.nil?
52
+ end
53
+
54
+ # @since 0.7.0
55
+ # @api private
56
+ def password
57
+ " -p #{@uri.password}" unless @uri.password.nil?
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end