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
@@ -1,296 +0,0 @@
1
- require 'hanami/model/adapters/abstract'
2
- require 'hanami/model/adapters/implementation'
3
- require 'hanami/model/adapters/sql/collection'
4
- require 'hanami/model/adapters/sql/command'
5
- require 'hanami/model/adapters/sql/query'
6
- require 'hanami/model/adapters/sql/console'
7
- require 'sequel'
8
-
9
- module Hanami
10
- module Model
11
- module Adapters
12
- # Adapter for SQL databases
13
- #
14
- # In order to use it with a specific database, you must require the Ruby
15
- # gem before of loading Hanami::Model.
16
- #
17
- # @see Hanami::Model::Adapters::Implementation
18
- #
19
- # @api private
20
- # @since 0.1.0
21
- class SqlAdapter < Abstract
22
- include Implementation
23
-
24
- # Initialize the adapter.
25
- #
26
- # Hanami::Model uses Sequel. For a complete reference of the connection
27
- # URI, please see: http://sequel.jeremyevans.net/rdoc/files/doc/opening_databases_rdoc.html
28
- #
29
- # @param mapper [Object] the database mapper
30
- # @param uri [String] the connection uri for the database
31
- # @param options [Hash] a hash of non-mandatory adapter options
32
- #
33
- # @return [Hanami::Model::Adapters::SqlAdapter]
34
- #
35
- # @raise [Hanami::Model::Adapters::DatabaseAdapterNotFound] if the given
36
- # URI refers to an unknown or not registered adapter.
37
- #
38
- # @raise [URI::InvalidURIError] if the given URI is malformed
39
- #
40
- # @see Hanami::Model::Mapper
41
- # @see http://sequel.jeremyevans.net/rdoc/files/doc/opening_databases_rdoc.html
42
- #
43
- # @api private
44
- # @since 0.1.0
45
- def initialize(mapper, uri, options = {})
46
- super
47
- @connection = Sequel.connect(@uri, @options)
48
- rescue Sequel::AdapterNotFound => e
49
- raise DatabaseAdapterNotFound.new(e.message)
50
- end
51
-
52
- # Creates a record in the database for the given entity.
53
- # It assigns the `id` attribute, in case of success.
54
- #
55
- # @param collection [Symbol] the target collection (it must be mapped).
56
- # @param entity [#id=] the entity to create
57
- #
58
- # @return [Object] the entity
59
- #
60
- # @api private
61
- # @since 0.1.0
62
- def create(collection, entity)
63
- command(
64
- query(collection)
65
- ).create(entity)
66
- end
67
-
68
- # Updates a record in the database corresponding to the given entity.
69
- #
70
- # @param collection [Symbol] the target collection (it must be mapped).
71
- # @param entity [#id] the entity to update
72
- #
73
- # @return [Object] the entity
74
- #
75
- # @api private
76
- # @since 0.1.0
77
- def update(collection, entity)
78
- command(
79
- _find(collection, entity.id)
80
- ).update(entity)
81
- end
82
-
83
- # Deletes a record in the database corresponding to the given entity.
84
- #
85
- # @param collection [Symbol] the target collection (it must be mapped).
86
- # @param entity [#id] the entity to delete
87
- #
88
- # @api private
89
- # @since 0.1.0
90
- def delete(collection, entity)
91
- command(
92
- _find(collection, entity.id)
93
- ).delete
94
- end
95
-
96
- # Deletes all the records from the given collection.
97
- #
98
- # @param collection [Symbol] the target collection (it must be mapped).
99
- #
100
- # @api private
101
- # @since 0.1.0
102
- def clear(collection)
103
- command(query(collection)).clear
104
- end
105
-
106
- # Fabricates a command for the given query.
107
- #
108
- # @param query [Hanami::Model::Adapters::Sql::Query] the query object to
109
- # act on.
110
- #
111
- # @return [Hanami::Model::Adapters::Sql::Command]
112
- #
113
- # @see Hanami::Model::Adapters::Sql::Command
114
- #
115
- # @api private
116
- # @since 0.1.0
117
- def command(query)
118
- Sql::Command.new(query)
119
- end
120
-
121
- # Fabricates a query
122
- #
123
- # @param collection [Symbol] the target collection (it must be mapped).
124
- # @param blk [Proc] a block of code to be executed in the context of
125
- # the query.
126
- #
127
- # @return [Hanami::Model::Adapters::Sql::Query]
128
- #
129
- # @see Hanami::Model::Adapters::Sql::Query
130
- #
131
- # @api private
132
- # @since 0.1.0
133
- def query(collection, context = nil, &blk)
134
- Sql::Query.new(_collection(collection), context, &blk)
135
- end
136
-
137
- # Wraps the given block in a transaction.
138
- #
139
- # For performance reasons the block isn't in the signature of the method,
140
- # but it's yielded at the lower level.
141
- #
142
- # @param options [Hash] options for transaction
143
- # @option rollback [Symbol] the optional rollback policy: `:always` or
144
- # `:reraise`.
145
- #
146
- # @see Hanami::Repository::ClassMethods#transaction
147
- #
148
- # @since 0.2.3
149
- # @api private
150
- #
151
- # @example Basic usage
152
- # require 'hanami/model'
153
- #
154
- # class Article
155
- # include Hanami::Entity
156
- # attributes :title, :body
157
- # end
158
- #
159
- # class ArticleRepository
160
- # include Hanami::Repository
161
- # end
162
- #
163
- # article = Article.new(title: 'Introducing transactions',
164
- # body: 'lorem ipsum')
165
- #
166
- # ArticleRepository.transaction do
167
- # ArticleRepository.dangerous_operation!(article) # => RuntimeError
168
- # # !!! ROLLBACK !!!
169
- # end
170
- #
171
- # @example Policy rollback always
172
- # require 'hanami/model'
173
- #
174
- # class Article
175
- # include Hanami::Entity
176
- # attributes :title, :body
177
- # end
178
- #
179
- # class ArticleRepository
180
- # include Hanami::Repository
181
- # end
182
- #
183
- # article = Article.new(title: 'Introducing transactions',
184
- # body: 'lorem ipsum')
185
- #
186
- # ArticleRepository.transaction(rollback: :always) do
187
- # ArticleRepository.create(article)
188
- # # !!! ROLLBACK !!!
189
- # end
190
- #
191
- # # The operation is rolled back, even in no exceptions were raised.
192
- #
193
- # @example Policy rollback reraise
194
- # require 'hanami/model'
195
- #
196
- # class Article
197
- # include Hanami::Entity
198
- # attributes :title, :body
199
- # end
200
- #
201
- # class ArticleRepository
202
- # include Hanami::Repository
203
- # end
204
- #
205
- # article = Article.new(title: 'Introducing transactions',
206
- # body: 'lorem ipsum')
207
- #
208
- # ArticleRepository.transaction(rollback: :reraise) do
209
- # ArticleRepository.dangerous_operation!(article) # => RuntimeError
210
- # # !!! ROLLBACK !!!
211
- # end # => RuntimeError
212
- #
213
- # # The operation is rolled back, but RuntimeError is re-raised.
214
- def transaction(options = {})
215
- @connection.transaction(options) do
216
- yield
217
- end
218
- end
219
-
220
- # Returns a string which can be executed to start a console suitable
221
- # for the configured database, adding the necessary CLI flags, such as
222
- # url, password, port number etc.
223
- #
224
- # @return [String]
225
- #
226
- # @since 0.3.0
227
- def connection_string
228
- Sql::Console.new(@uri).connection_string
229
- end
230
-
231
- # Executes a raw SQL command
232
- #
233
- # @param raw [String] the raw SQL statement to execute on the connection
234
- #
235
- # @raise [Hanami::Model::InvalidCommandError] if the raw SQL statement is invalid
236
- #
237
- # @return [NilClass]
238
- #
239
- # @since 0.3.1
240
- def execute(raw)
241
- begin
242
- @connection.execute(raw)
243
- nil
244
- rescue Sequel::DatabaseError => e
245
- raise Hanami::Model::InvalidCommandError.new(e.message)
246
- end
247
- end
248
-
249
- # Fetches raw result sets for the given SQL query
250
- #
251
- # @param raw [String] the raw SQL query
252
- # @param blk [Proc] optional block that is yielded for each record
253
- #
254
- # @return [Array]
255
- #
256
- # @raise [Hanami::Model::InvalidQueryError] if the raw SQL statement is invalid
257
- #
258
- # @since 0.5.0
259
- def fetch(raw, &blk)
260
- if block_given?
261
- @connection.fetch(raw, &blk)
262
- else
263
- @connection.fetch(raw).to_a
264
- end
265
- rescue Sequel::DatabaseError => e
266
- raise Hanami::Model::InvalidQueryError.new(e.message)
267
- end
268
-
269
- # @api private
270
- # @since 0.5.0
271
- #
272
- # @see Hanami::Model::Adapters::Abstract#disconnect
273
- def disconnect
274
- @connection.disconnect
275
- @connection = DisconnectedResource.new
276
- end
277
-
278
- private
279
-
280
- # Returns a collection from the given name.
281
- #
282
- # @param name [Symbol] a name of the collection (it must be mapped).
283
- #
284
- # @return [Hanami::Model::Adapters::Sql::Collection]
285
- #
286
- # @see Hanami::Model::Adapters::Sql::Collection
287
- #
288
- # @api private
289
- # @since 0.1.0
290
- def _collection(name)
291
- Sql::Collection.new(@connection[name], _mapped_collection(name))
292
- end
293
- end
294
- end
295
- end
296
- end
@@ -1,74 +0,0 @@
1
- module Hanami
2
- module Model
3
- # Abstract coercer
4
- #
5
- # It can be used as super class for custom mapping coercers.
6
- #
7
- # @since 0.5.0
8
- #
9
- # @see Hanami::Model::Mapper
10
- #
11
- # @example Postgres Array
12
- # require 'hanami/model/coercer'
13
- # require 'sequel/extensions/pg_array'
14
- #
15
- # class PGArray < Hanami::Model::Coercer
16
- # def self.dump(value)
17
- # ::Sequel.pg_array(value) rescue nil
18
- # end
19
- #
20
- # def self.load(value)
21
- # ::Kernel.Array(value) unless value.nil?
22
- # end
23
- # end
24
- #
25
- # Hanami::Model.configure do
26
- # mapping do
27
- # collection :articles do
28
- # entity Article
29
- # repository ArticleRepository
30
- #
31
- # attribute :id, Integer
32
- # attribute :title, String
33
- # attribute :tags, PGArray
34
- # end
35
- # end
36
- # end.load!
37
- #
38
- # # When the entity is serialized, it calls `PGArray.dump` to store `tags`
39
- # # as a Postgres Array.
40
- # #
41
- # # When the record is loaded (unserialized) from the database, it calls
42
- # # `PGArray.load` and returns a Ruby Array.
43
- class Coercer
44
- # Deserialize (load) a value coming from the database into a Ruby object.
45
- #
46
- # When inheriting from this class, it's a good practice to return <tt>nil</tt>
47
- # if the given value it's <tt>nil</tt>.
48
- #
49
- # @abstract
50
- #
51
- # @raise [TypeError] if the value can't be coerced
52
- #
53
- # @since 0.5.0
54
- #
55
- # @see Hanami::Model::Mapping::Coercers
56
- def self.load(value)
57
- raise NotImplementedError
58
- end
59
-
60
- # Serialize (dump) a Ruby object into a value that can be store by the database.
61
- #
62
- # @abstract
63
- #
64
- # @raise [TypeError] if the value can't be coerced
65
- #
66
- # @since 0.5.0
67
- #
68
- # @see Hanami::Model::Mapping::Coercers
69
- def self.dump(value)
70
- self.load(value)
71
- end
72
- end
73
- end
74
- end
@@ -1,116 +0,0 @@
1
- require 'hanami/utils/class'
2
-
3
- module Hanami
4
- module Model
5
- module Config
6
- # Raised when an adapter class does not exist
7
- #
8
- # @since 0.2.0
9
- class AdapterNotFound < Hanami::Model::Error
10
- def initialize(adapter_name)
11
- super "Cannot find Hanami::Model adapter #{adapter_name}"
12
- end
13
- end
14
-
15
- # Configuration for the adapter
16
- #
17
- # Hanami::Model has its own global configuration that can be manipulated
18
- # via `Hanami::Model.configure`.
19
- #
20
- # New adapter configuration can be registered via `Hanami::Model.adapter`.
21
- #
22
- # @see Hanami::Model.adapter
23
- #
24
- # @example
25
- # require 'hanami/model'
26
- #
27
- # Hanami::Model.configure do
28
- # adapter type: :sql, uri: 'postgres://localhost/database'
29
- # end
30
- #
31
- # Hanami::Model.configuration.adapter_config
32
- # # => Hanami::Model::Config::Adapter(type: :sql, uri: 'postgres://localhost/database')
33
- #
34
- # By convention, Hanami inflects type to find the adapter class
35
- # For example, if type is :sql, derived class will be `Hanami::Model::Adapters::SqlAdapter`
36
- #
37
- # @since 0.2.0
38
- class Adapter
39
- # @return [Symbol] the adapter name
40
- #
41
- # @since 0.2.0
42
- attr_reader :type
43
-
44
- # @return [String] the adapter URI
45
- #
46
- # @since 0.2.0
47
- attr_reader :uri
48
-
49
- # @return [Hash] a list of non-mandatory options for the adapter
50
- #
51
- attr_reader :options
52
-
53
- # @return [String] the adapter class name
54
- #
55
- # @since 0.2.0
56
- attr_reader :class_name
57
-
58
- # Initialize an adapter configuration instance
59
- #
60
- # @param options [Hash] configuration options
61
- # @option options [Symbol] :type adapter type name
62
- # @option options [String] :uri adapter URI
63
- #
64
- # @return [Hanami::Model::Config::Adapter] a new apdapter configuration's
65
- # instance
66
- #
67
- # @since 0.2.0
68
- def initialize(**options)
69
- opts = options.dup
70
-
71
- @type = opts.delete(:type)
72
- @uri = opts.delete(:uri)
73
- @options = opts
74
-
75
- @class_name ||= Hanami::Utils::String.new("#{@type}_adapter").classify
76
- end
77
-
78
- # Initialize the adapter
79
- #
80
- # @param mapper [Hanami::Model::Mapper] the mapper instance
81
- #
82
- # @return [Hanami::Model::Adapters::SqlAdapter, Hanami::Model::Adapters::MemoryAdapter] an adapter instance
83
- #
84
- # @see Hanami::Model::Adapters
85
- #
86
- # @since 0.2.0
87
- def build(mapper)
88
- load_adapter
89
- instantiate_adapter(mapper)
90
- end
91
-
92
- private
93
-
94
- def load_adapter
95
- begin
96
- require "hanami/model/adapters/#{type}_adapter"
97
- rescue LoadError => e
98
- raise LoadError.new("Cannot find Hanami::Model adapter '#{type}' (#{e.message})")
99
- end
100
- end
101
-
102
- def instantiate_adapter(mapper)
103
- begin
104
- klass = Hanami::Utils::Class.load!(class_name, Hanami::Model::Adapters)
105
- klass.new(mapper, uri, options)
106
- rescue NameError
107
- raise AdapterNotFound.new(class_name)
108
- rescue => e
109
- raise "Cannot instantiate adapter of #{klass} (#{e.message})"
110
- end
111
- end
112
-
113
- end
114
- end
115
- end
116
- end