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,281 +0,0 @@
1
- require 'hanami/utils/basic_object'
2
-
3
- module Hanami
4
- module Model
5
- module Adapters
6
- # It's raised when an adapter can't find the underlying database adapter.
7
- #
8
- # Example: When we try to use the SqlAdapter with a Postgres database
9
- # but we didn't loaded the pg gem before.
10
- #
11
- # @see Hanami::Model::Adapters::SqlAdapter#initialize
12
- #
13
- # @since 0.1.0
14
- class DatabaseAdapterNotFound < Hanami::Model::Error
15
- end
16
-
17
- # It's raised when an adapter does not support a feature.
18
- #
19
- # Example: When we try to get a connection string for the current database
20
- # but the adapter has not implemented it.
21
- #
22
- # @see Hanami::Model::Adapters::Abstract#connection_string
23
- #
24
- # @since 0.3.0
25
- class NotSupportedError < Hanami::Model::Error
26
- end
27
-
28
- # It's raised when an operation is requested to an adapter after it was
29
- # disconnected.
30
- #
31
- # @since 0.5.0
32
- class DisconnectedAdapterError < Hanami::Model::Error
33
- def initialize
34
- super "You have tried to perform an operation on a disconnected adapter"
35
- end
36
- end
37
-
38
- # Represents a disconnected resource.
39
- #
40
- # When we use <tt>#disconnect</tt> for <tt>MemoryAdapter</tt> and
41
- # </tt>FileSystemAdapter</tt>, we want to free underlying resources such
42
- # as a mutex or a file descriptor.
43
- #
44
- # These adapters use to use anonymous descriptors that are destroyed by
45
- # Ruby VM after each operation. Sometimes we need to clean the state and
46
- # start fresh (eg. during a test suite or a deploy).
47
- #
48
- # Instead of assign <tt>nil</tt> to these instance variables, we assign this
49
- # special type: <tt>DisconnectedResource</tt>.
50
- #
51
- # In case an operation is still performed after the adapter was disconnected,
52
- # instead of see a generic <tt>NoMethodError</tt> for <tt>nil</tt>, a developer
53
- # will face a specific message relative to the state of the adapter.
54
- #
55
- # @api private
56
- # @since 0.5.0
57
- #
58
- # @see Hanami::Model::Adapters::Abstract#disconnect
59
- # @see Hanami::Model::Adapters::MemoryAdapter#disconnect
60
- # @see Hanami::Model::Adapters::FileSystemAdapter#disconnect
61
- class DisconnectedResource < Utils::BasicObject
62
- def method_missing(method_name, *)
63
- ::Kernel.raise DisconnectedAdapterError.new
64
- end
65
- end
66
-
67
- # Abstract adapter.
68
- #
69
- # An adapter is a concrete implementation that allows a repository to
70
- # communicate with a single database.
71
- #
72
- # Hanami::Model is shipped with Memory and SQL adapters.
73
- # Third part adapters MUST implement the interface defined here.
74
- # For convenience they may inherit from this class.
75
- #
76
- # These are low level details, and shouldn't be used directly.
77
- # Please use a repository for entities persistence.
78
- #
79
- # @since 0.1.0
80
- class Abstract
81
- # Initialize the adapter
82
- #
83
- # @param mapper [Hanami::Model::Mapper] the object that defines the
84
- # database to entities mapping
85
- #
86
- # @param uri [String] the optional connection string to the database
87
- #
88
- # @param options [Hash] a list of non-mandatory adapter options
89
- #
90
- # @since 0.1.0
91
- def initialize(mapper, uri = nil, options = {})
92
- @mapper = mapper
93
- @uri = uri
94
- @options = options
95
- end
96
-
97
- # Creates or updates a record in the database for the given entity.
98
- #
99
- # @param collection [Symbol] the target collection (it must be mapped).
100
- # @param entity [Object] the entity to persist
101
- #
102
- # @return [Object] the entity
103
- #
104
- # @since 0.1.0
105
- def persist(collection, entity)
106
- raise NotImplementedError
107
- end
108
-
109
- # Creates a record in the database for the given entity.
110
- # It should assign an id (identity) to the entity in case of success.
111
- #
112
- # @param collection [Symbol] the target collection (it must be mapped).
113
- # @param entity [Object] the entity to create
114
- #
115
- # @return [Object] the entity
116
- #
117
- # @since 0.1.0
118
- def create(collection, entity)
119
- raise NotImplementedError
120
- end
121
-
122
- # Updates a record in the database corresponding to the given entity.
123
- #
124
- # @param collection [Symbol] the target collection (it must be mapped).
125
- # @param entity [Object] the entity to update
126
- #
127
- # @return [Object] the entity
128
- #
129
- # @since 0.1.0
130
- def update(collection, entity)
131
- raise NotImplementedError
132
- end
133
-
134
- # Deletes a record in the database corresponding to the given entity.
135
- #
136
- # @param collection [Symbol] the target collection (it must be mapped).
137
- # @param entity [Object] the entity to delete
138
- #
139
- # @since 0.1.0
140
- def delete(collection, entity)
141
- raise NotImplementedError
142
- end
143
-
144
- # Returns all the records for the given collection
145
- #
146
- # @param collection [Symbol] the target collection (it must be mapped).
147
- #
148
- # @return [Array] all the records
149
- #
150
- # @since 0.1.0
151
- def all(collection)
152
- raise NotImplementedError
153
- end
154
-
155
- # Returns a unique record from the given collection, with the given
156
- # identity.
157
- #
158
- # @param collection [Symbol] the target collection (it must be mapped).
159
- # @param id [Object] the identity of the object.
160
- #
161
- # @return [Object] the entity
162
- #
163
- # @since 0.1.0
164
- def find(collection, id)
165
- raise NotImplementedError
166
- end
167
-
168
- # Returns the first record in the given collection.
169
- #
170
- # @param collection [Symbol] the target collection (it must be mapped).
171
- #
172
- # @return [Object] the first entity
173
- #
174
- # @since 0.1.0
175
- def first(collection)
176
- raise NotImplementedError
177
- end
178
-
179
- # Returns the last record in the given collection.
180
- #
181
- # @param collection [Symbol] the target collection (it must be mapped).
182
- #
183
- # @return [Object] the last entity
184
- #
185
- # @since 0.1.0
186
- def last(collection)
187
- raise NotImplementedError
188
- end
189
-
190
- # Empties the given collection.
191
- #
192
- # @param collection [Symbol] the target collection (it must be mapped).
193
- #
194
- # @since 0.1.0
195
- def clear(collection)
196
- raise NotImplementedError
197
- end
198
-
199
- # Executes a command for the given query.
200
- #
201
- # @param query [Object] the query object to act on.
202
- #
203
- # @since 0.1.0
204
- def command(query)
205
- raise NotImplementedError
206
- end
207
-
208
- # Returns a query
209
- #
210
- # @param collection [Symbol] the target collection (it must be mapped).
211
- # @param blk [Proc] a block of code to be executed in the context of
212
- # the query.
213
- #
214
- # @return [Object]
215
- #
216
- # @since 0.1.0
217
- def query(collection, &blk)
218
- raise NotImplementedError
219
- end
220
-
221
- # Wraps the given block in a transaction.
222
- #
223
- # For performance reasons the block isn't in the signature of the method,
224
- # but it's yielded at the lower level.
225
- #
226
- # Please note that it's only supported by some databases.
227
- # For this reason, the options may vary from adapter to adapter.
228
- #
229
- # @param options [Hash] options for transaction
230
- #
231
- # @see Hanami::Model::Adapters::SqlAdapter#transaction
232
- # @see Hanami::Model::Adapters::MemoryAdapter#transaction
233
- #
234
- # @since 0.2.3
235
- def transaction(options = {})
236
- raise NotImplementedError
237
- end
238
-
239
- # Returns a string which can be executed to start a console suitable
240
- # for the configured database.
241
- #
242
- # @return [String] to be executed to start a database console
243
- #
244
- # @since 0.3.0
245
- def connection_string
246
- raise NotSupportedError
247
- end
248
-
249
- # Executes a raw command
250
- #
251
- # @param raw [String] the raw statement to execute on the connection
252
- #
253
- # @return [NilClass]
254
- #
255
- # @since 0.3.1
256
- def execute(raw)
257
- raise NotImplementedError
258
- end
259
-
260
- # Fetches raw records from
261
- #
262
- # @param raw [String] the raw query
263
- # @param blk [Proc] an optional block that is yielded for each record
264
- #
265
- # @return [Enumerable<Hash>, Array<Hash>]
266
- #
267
- # @since 0.5.0
268
- def fetch(raw, &blk)
269
- raise NotImplementedError
270
- end
271
-
272
- # Disconnects the connection by freeing low level resources
273
- #
274
- # @since 0.5.0
275
- def disconnect
276
- raise NotImplementedError
277
- end
278
- end
279
- end
280
- end
281
- end
@@ -1,288 +0,0 @@
1
- require 'thread'
2
- require 'pathname'
3
- require 'hanami/model/adapters/memory_adapter'
4
-
5
- module Hanami
6
- module Model
7
- module Adapters
8
- # In memory adapter with file system persistence.
9
- # It behaves like the SQL adapter, but it doesn't support all the SQL
10
- # features offered by that kind of databases.
11
- #
12
- # This adapter SHOULD be used only for development or testing purposes.
13
- # Each read/write operation is wrapped by a `Mutex` and persisted to the
14
- # disk.
15
- #
16
- # For those reasons it's really unefficient, but great for quick
17
- # prototyping as it's schema-less.
18
- #
19
- # It works exactly like the `MemoryAdapter`, with the only difference
20
- # that it persist data to the disk.
21
- #
22
- # The persistence policy uses Ruby `Marshal` `dump` and `load` operations.
23
- # Please be aware of the limitations this model.
24
- #
25
- # @see Hanami::Model::Adapters::Implementation
26
- # @see Hanami::Model::Adapters::MemoryAdapter
27
- # @see http://www.ruby-doc.org/core/Marshal.html
28
- #
29
- # @api private
30
- # @since 0.2.0
31
- class FileSystemAdapter < MemoryAdapter
32
- # Default writing mode
33
- #
34
- # Binary, write only, create file if missing or erase if don't.
35
- #
36
- # @see http://ruby-doc.org/core/File/Constants.html
37
- #
38
- # @since 0.2.0
39
- # @api private
40
- WRITING_MODE = File::WRONLY|File::BINARY|File::CREAT
41
-
42
- # Default chmod
43
- #
44
- # @see http://en.wikipedia.org/wiki/Chmod
45
- #
46
- # @since 0.2.0
47
- # @api private
48
- CHMOD = 0644
49
-
50
- # File scheme
51
- #
52
- # @see https://tools.ietf.org/html/rfc3986
53
- #
54
- # @since 0.2.0
55
- # @api private
56
- FILE_SCHEME = 'file:///'.freeze
57
-
58
- # Initialize the adapter.
59
- #
60
- # @param mapper [Object] the database mapper
61
- # @param uri [String] the connection uri
62
- # @param options [Hash] a hash of non-mandatory adapter options
63
- #
64
- # @return [Hanami::Model::Adapters::FileSystemAdapter]
65
- #
66
- # @see Hanami::Model::Mapper
67
- #
68
- # @api private
69
- # @since 0.2.0
70
- def initialize(mapper, uri, options = {})
71
- super
72
- prepare(uri)
73
-
74
- @_mutex = Mutex.new
75
- end
76
-
77
- # Returns all the records for the given collection
78
- #
79
- # @param collection [Symbol] the target collection (it must be mapped).
80
- #
81
- # @return [Array] all the records
82
- #
83
- # @api private
84
- # @since 0.2.0
85
- def all(collection)
86
- _synchronize do
87
- read(collection)
88
- super
89
- end
90
- end
91
-
92
- # Returns a unique record from the given collection, with the given
93
- # id.
94
- #
95
- # @param collection [Symbol] the target collection (it must be mapped).
96
- # @param id [Object] the identity of the object.
97
- #
98
- # @return [Object] the entity
99
- #
100
- # @api private
101
- # @since 0.2.0
102
- def find(collection, id)
103
- _synchronize do
104
- read(collection)
105
- super
106
- end
107
- end
108
-
109
- # Returns the first record in the given collection.
110
- #
111
- # @param collection [Symbol] the target collection (it must be mapped).
112
- #
113
- # @return [Object] the first entity
114
- #
115
- # @api private
116
- # @since 0.2.0
117
- def first(collection)
118
- _synchronize do
119
- read(collection)
120
- super
121
- end
122
- end
123
-
124
- # Returns the last record in the given collection.
125
- #
126
- # @param collection [Symbol] the target collection (it must be mapped).
127
- #
128
- # @return [Object] the last entity
129
- #
130
- # @api private
131
- # @since 0.2.0
132
- def last(collection)
133
- _synchronize do
134
- read(collection)
135
- super
136
- end
137
- end
138
-
139
- # Creates a record in the database for the given entity.
140
- # It assigns the `id` attribute, in case of success.
141
- #
142
- # @param collection [Symbol] the target collection (it must be mapped).
143
- # @param entity [#id=] the entity to create
144
- #
145
- # @return [Object] the entity
146
- #
147
- # @api private
148
- # @since 0.2.0
149
- def create(collection, entity)
150
- _synchronize do
151
- super.tap { write(collection) }
152
- end
153
- end
154
-
155
- # Updates a record in the database corresponding to the given entity.
156
- #
157
- # @param collection [Symbol] the target collection (it must be mapped).
158
- # @param entity [#id] the entity to update
159
- #
160
- # @return [Object] the entity
161
- #
162
- # @api private
163
- # @since 0.2.0
164
- def update(collection, entity)
165
- _synchronize do
166
- super.tap { write(collection) }
167
- end
168
- end
169
-
170
- # Deletes a record in the database corresponding to the given entity.
171
- #
172
- # @param collection [Symbol] the target collection (it must be mapped).
173
- # @param entity [#id] the entity to delete
174
- #
175
- # @api private
176
- # @since 0.2.0
177
- def delete(collection, entity)
178
- _synchronize do
179
- super
180
- write(collection)
181
- end
182
- end
183
-
184
- # Deletes all the records from the given collection and resets the
185
- # identity counter.
186
- #
187
- # @param collection [Symbol] the target collection (it must be mapped).
188
- #
189
- # @api private
190
- # @since 0.2.0
191
- def clear(collection)
192
- _synchronize do
193
- super
194
- write(collection)
195
- end
196
- end
197
-
198
- # Fabricates a query
199
- #
200
- # @param collection [Symbol] the target collection (it must be mapped).
201
- # @param blk [Proc] a block of code to be executed in the context of
202
- # the query.
203
- #
204
- # @return [Hanami::Model::Adapters::Memory::Query]
205
- #
206
- # @see Hanami::Model::Adapters::Memory::Query
207
- #
208
- # @api private
209
- # @since 0.2.0
210
- def query(collection, context = nil, &blk)
211
- # _synchronize do
212
- read(collection)
213
- super
214
- # end
215
- end
216
-
217
- # Database informations
218
- #
219
- # @return [Hash] per collection informations
220
- #
221
- # @api private
222
- # @since 0.2.0
223
- def info
224
- @collections.each_with_object({}) do |(collection,_), result|
225
- result[collection] = query(collection).count
226
- end
227
- end
228
-
229
- # @api private
230
- # @since 0.5.0
231
- #
232
- # @see Hanami::Model::Adapters::Abstract#disconnect
233
- def disconnect
234
- super
235
-
236
- @_mutex = DisconnectedResource.new
237
- @root = DisconnectedResource.new
238
- end
239
-
240
- private
241
- # @api private
242
- # @since 0.2.0
243
- def prepare(uri)
244
- @root = Pathname.new(uri.sub(FILE_SCHEME, ''))
245
- @root.mkpath
246
-
247
- # Eager load previously persisted data.
248
- @root.each_child do |collection|
249
- collection = collection.basename.to_s.to_sym
250
- read(collection)
251
- end
252
- end
253
-
254
- # @api private
255
- # @since 0.2.0
256
- def _synchronize
257
- @_mutex.synchronize { yield }
258
- end
259
-
260
- # @api private
261
- # @since 0.2.0
262
- def write(collection)
263
- path = @root.join("#{ collection }")
264
- path.open(WRITING_MODE, CHMOD) {|f| f.write _dump( @collections.fetch(collection) ) }
265
- end
266
-
267
- # @api private
268
- # @since 0.2.0
269
- def read(collection)
270
- path = @root.join("#{ collection }")
271
- @collections[collection] = _load(path.read) if path.exist?
272
- end
273
-
274
- # @api private
275
- # @since 0.2.0
276
- def _dump(contents)
277
- Marshal.dump(contents)
278
- end
279
-
280
- # @api private
281
- # @since 0.2.0
282
- def _load(contents)
283
- Marshal.load(contents)
284
- end
285
- end
286
- end
287
- end
288
- end