hanami-model 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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