hanami-model 0.0.0 → 0.6.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +145 -0
  3. data/EXAMPLE.md +212 -0
  4. data/LICENSE.md +22 -0
  5. data/README.md +600 -7
  6. data/hanami-model.gemspec +17 -12
  7. data/lib/hanami-model.rb +1 -0
  8. data/lib/hanami/entity.rb +298 -0
  9. data/lib/hanami/entity/dirty_tracking.rb +74 -0
  10. data/lib/hanami/model.rb +204 -2
  11. data/lib/hanami/model/adapters/abstract.rb +281 -0
  12. data/lib/hanami/model/adapters/file_system_adapter.rb +288 -0
  13. data/lib/hanami/model/adapters/implementation.rb +111 -0
  14. data/lib/hanami/model/adapters/memory/collection.rb +132 -0
  15. data/lib/hanami/model/adapters/memory/command.rb +113 -0
  16. data/lib/hanami/model/adapters/memory/query.rb +653 -0
  17. data/lib/hanami/model/adapters/memory_adapter.rb +179 -0
  18. data/lib/hanami/model/adapters/null_adapter.rb +24 -0
  19. data/lib/hanami/model/adapters/sql/collection.rb +287 -0
  20. data/lib/hanami/model/adapters/sql/command.rb +73 -0
  21. data/lib/hanami/model/adapters/sql/console.rb +33 -0
  22. data/lib/hanami/model/adapters/sql/consoles/mysql.rb +49 -0
  23. data/lib/hanami/model/adapters/sql/consoles/postgresql.rb +48 -0
  24. data/lib/hanami/model/adapters/sql/consoles/sqlite.rb +26 -0
  25. data/lib/hanami/model/adapters/sql/query.rb +788 -0
  26. data/lib/hanami/model/adapters/sql_adapter.rb +296 -0
  27. data/lib/hanami/model/coercer.rb +74 -0
  28. data/lib/hanami/model/config/adapter.rb +116 -0
  29. data/lib/hanami/model/config/mapper.rb +45 -0
  30. data/lib/hanami/model/configuration.rb +275 -0
  31. data/lib/hanami/model/error.rb +7 -0
  32. data/lib/hanami/model/mapper.rb +124 -0
  33. data/lib/hanami/model/mapping.rb +48 -0
  34. data/lib/hanami/model/mapping/attribute.rb +85 -0
  35. data/lib/hanami/model/mapping/coercers.rb +314 -0
  36. data/lib/hanami/model/mapping/collection.rb +490 -0
  37. data/lib/hanami/model/mapping/collection_coercer.rb +79 -0
  38. data/lib/hanami/model/migrator.rb +324 -0
  39. data/lib/hanami/model/migrator/adapter.rb +170 -0
  40. data/lib/hanami/model/migrator/connection.rb +133 -0
  41. data/lib/hanami/model/migrator/mysql_adapter.rb +72 -0
  42. data/lib/hanami/model/migrator/postgres_adapter.rb +119 -0
  43. data/lib/hanami/model/migrator/sqlite_adapter.rb +110 -0
  44. data/lib/hanami/model/version.rb +4 -1
  45. data/lib/hanami/repository.rb +872 -0
  46. metadata +100 -16
  47. data/.gitignore +0 -9
  48. data/Gemfile +0 -4
  49. data/Rakefile +0 -2
  50. data/bin/console +0 -14
  51. data/bin/setup +0 -8
@@ -0,0 +1,179 @@
1
+ require 'hanami/model/adapters/abstract'
2
+ require 'hanami/model/adapters/implementation'
3
+ require 'hanami/model/adapters/memory/collection'
4
+ require 'hanami/model/adapters/memory/command'
5
+ require 'hanami/model/adapters/memory/query'
6
+
7
+ module Hanami
8
+ module Model
9
+ module Adapters
10
+ # In memory adapter that behaves like a SQL database.
11
+ # Not all the features of the SQL adapter are supported.
12
+ #
13
+ # This adapter SHOULD be used only for development or testing purposes,
14
+ # because its computations are inefficient and the data is volatile.
15
+ #
16
+ # @see Hanami::Model::Adapters::Implementation
17
+ #
18
+ # @api private
19
+ # @since 0.1.0
20
+ class MemoryAdapter < Abstract
21
+ include Implementation
22
+
23
+ # Initialize the adapter.
24
+ #
25
+ # @param mapper [Object] the database mapper
26
+ # @param uri [String] the connection uri (ignored)
27
+ # @param options [Hash] a hash of non mandatory adapter options
28
+ #
29
+ # @return [Hanami::Model::Adapters::MemoryAdapter]
30
+ #
31
+ # @see Hanami::Model::Mapper
32
+ #
33
+ # @api private
34
+ # @since 0.1.0
35
+ def initialize(mapper, uri = nil, options = {})
36
+ super
37
+
38
+ @mutex = Mutex.new
39
+ @collections = {}
40
+ end
41
+
42
+ # Creates a record in the database for the given entity.
43
+ # It assigns the `id` attribute, in case of success.
44
+ #
45
+ # @param collection [Symbol] the target collection (it must be mapped).
46
+ # @param entity [#id=] the entity to create
47
+ #
48
+ # @return [Object] the entity
49
+ #
50
+ # @api private
51
+ # @since 0.1.0
52
+ def create(collection, entity)
53
+ synchronize do
54
+ command(collection).create(entity)
55
+ end
56
+ end
57
+
58
+ # Updates a record in the database corresponding to the given entity.
59
+ #
60
+ # @param collection [Symbol] the target collection (it must be mapped).
61
+ # @param entity [#id] the entity to update
62
+ #
63
+ # @return [Object] the entity
64
+ #
65
+ # @api private
66
+ # @since 0.1.0
67
+ def update(collection, entity)
68
+ synchronize do
69
+ command(collection).update(entity)
70
+ end
71
+ end
72
+
73
+ # Deletes a record in the database corresponding to the given entity.
74
+ #
75
+ # @param collection [Symbol] the target collection (it must be mapped).
76
+ # @param entity [#id] the entity to delete
77
+ #
78
+ # @api private
79
+ # @since 0.1.0
80
+ def delete(collection, entity)
81
+ synchronize do
82
+ command(collection).delete(entity)
83
+ end
84
+ end
85
+
86
+ # Deletes all the records from the given collection and resets the
87
+ # identity counter.
88
+ #
89
+ # @param collection [Symbol] the target collection (it must be mapped).
90
+ #
91
+ # @api private
92
+ # @since 0.1.0
93
+ def clear(collection)
94
+ synchronize do
95
+ command(collection).clear
96
+ end
97
+ end
98
+
99
+ # Fabricates a command for the given query.
100
+ #
101
+ # @param collection [Symbol] the collection name (it must be mapped)
102
+ #
103
+ # @return [Hanami::Model::Adapters::Memory::Command]
104
+ #
105
+ # @see Hanami::Model::Adapters::Memory::Command
106
+ #
107
+ # @api private
108
+ # @since 0.1.0
109
+ def command(collection)
110
+ Memory::Command.new(_collection(collection), _mapped_collection(collection))
111
+ end
112
+
113
+ # Fabricates a query
114
+ #
115
+ # @param collection [Symbol] the target collection (it must be mapped).
116
+ # @param blk [Proc] a block of code to be executed in the context of
117
+ # the query.
118
+ #
119
+ # @return [Hanami::Model::Adapters::Memory::Query]
120
+ #
121
+ # @see Hanami::Model::Adapters::Memory::Query
122
+ #
123
+ # @api private
124
+ # @since 0.1.0
125
+ def query(collection, context = nil, &blk)
126
+ synchronize do
127
+ Memory::Query.new(_collection(collection), _mapped_collection(collection), &blk)
128
+ end
129
+ end
130
+
131
+ # WARNING: this is a no-op. For "real" transactions please use
132
+ # `SqlAdapter` or another adapter that supports them
133
+ #
134
+ # @param options [Hash] options for transaction
135
+ #
136
+ # @see Hanami::Model::Adapters::SqlAdapter#transaction
137
+ # @see Hanami::Model::Adapters::Abstract#transaction
138
+ #
139
+ # @since 0.2.3
140
+ def transaction(options = {})
141
+ yield
142
+ end
143
+
144
+ # @api private
145
+ # @since 0.5.0
146
+ #
147
+ # @see Hanami::Model::Adapters::Abstract#disconnect
148
+ def disconnect
149
+ @collections = DisconnectedResource.new
150
+ @mutex = DisconnectedResource.new
151
+ end
152
+
153
+ private
154
+
155
+ # Returns a collection from the given name.
156
+ #
157
+ # @param name [Symbol] a name of the collection (it must be mapped).
158
+ #
159
+ # @return [Hanami::Model::Adapters::Memory::Collection]
160
+ #
161
+ # @see Hanami::Model::Adapters::Memory::Collection
162
+ #
163
+ # @api private
164
+ # @since 0.1.0
165
+ def _collection(name)
166
+ @collections[name] ||= Memory::Collection.new(name, _identity(name))
167
+ end
168
+
169
+ # Executes the given block within a critical section.
170
+ #
171
+ # @api private
172
+ # @since 0.2.0
173
+ def synchronize
174
+ @mutex.synchronize { yield }
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,24 @@
1
+ require 'hanami/model/error'
2
+
3
+ module Hanami
4
+ module Model
5
+ module Adapters
6
+ # @since 0.2.0
7
+ class NoAdapterError < Hanami::Model::Error
8
+ def initialize(method_name)
9
+ super("Cannot invoke `#{ method_name }' on repository. "\
10
+ "Please check if `adapter' and `mapping' are set, "\
11
+ "and that you call `.load!' on the configuration.")
12
+ end
13
+ end
14
+
15
+ # @since 0.2.0
16
+ # @api private
17
+ class NullAdapter
18
+ def method_missing(m, *args)
19
+ raise NoAdapterError.new(m)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,287 @@
1
+ require 'delegate'
2
+ require 'hanami/utils/kernel' unless RUBY_VERSION >= '2.1'
3
+
4
+ module Hanami
5
+ module Model
6
+ module Adapters
7
+ module Sql
8
+ # Maps a SQL database table and perfoms manipulations on it.
9
+ #
10
+ # @api private
11
+ # @since 0.1.0
12
+ #
13
+ # @see http://sequel.jeremyevans.net/rdoc/files/doc/dataset_basics_rdoc.html
14
+ # @see http://sequel.jeremyevans.net/rdoc/files/doc/dataset_filtering_rdoc.html
15
+ class Collection < SimpleDelegator
16
+ # Initialize a collection
17
+ #
18
+ # @param dataset [Sequel::Dataset] the dataset that maps a table or a
19
+ # subset of it.
20
+ # @param mapped_collection [Hanami::Model::Mapping::Collection] a
21
+ # mapped collection
22
+ #
23
+ # @return [Hanami::Model::Adapters::Sql::Collection]
24
+ #
25
+ # @api private
26
+ # @since 0.1.0
27
+ def initialize(dataset, mapped_collection)
28
+ super(dataset)
29
+ @mapped_collection = mapped_collection
30
+ end
31
+
32
+ # Filters the current scope with an `exclude` directive.
33
+ #
34
+ # @param args [Array] the array of arguments
35
+ #
36
+ # @see Hanami::Model::Adapters::Sql::Query#exclude
37
+ #
38
+ # @return [Hanami::Model::Adapters::Sql::Collection] the filtered
39
+ # collection
40
+ #
41
+ # @api private
42
+ # @since 0.1.0
43
+ def exclude(*args)
44
+ Collection.new(super, @mapped_collection)
45
+ end
46
+
47
+ # Creates a record for the given entity and assigns an id.
48
+ #
49
+ # @param entity [Object] the entity to persist
50
+ #
51
+ # @see Hanami::Model::Adapters::Sql::Command#create
52
+ #
53
+ # @return the primary key of the created record
54
+ #
55
+ # @api private
56
+ # @since 0.1.0
57
+ def insert(entity)
58
+ serialized_entity = _serialize(entity)
59
+ serialized_entity[identity] = super(serialized_entity)
60
+
61
+ _deserialize(serialized_entity)
62
+ end
63
+
64
+ # Filters the current scope with a `limit` directive.
65
+ #
66
+ # @param args [Array] the array of arguments
67
+ #
68
+ # @see Hanami::Model::Adapters::Sql::Query#limit
69
+ #
70
+ # @return [Hanami::Model::Adapters::Sql::Collection] the filtered
71
+ # collection
72
+ #
73
+ # @api private
74
+ # @since 0.1.0
75
+ def limit(*args)
76
+ Collection.new(super, @mapped_collection)
77
+ end
78
+
79
+ # Filters the current scope with an `offset` directive.
80
+ #
81
+ # @param args [Array] the array of arguments
82
+ #
83
+ # @see Hanami::Model::Adapters::Sql::Query#offset
84
+ #
85
+ # @return [Hanami::Model::Adapters::Sql::Collection] the filtered
86
+ # collection
87
+ #
88
+ # @api private
89
+ # @since 0.1.0
90
+ def offset(*args)
91
+ Collection.new(super, @mapped_collection)
92
+ end
93
+
94
+ # Filters the current scope with an `or` directive.
95
+ #
96
+ # @param args [Array] the array of arguments
97
+ #
98
+ # @see Hanami::Model::Adapters::Sql::Query#or
99
+ #
100
+ # @return [Hanami::Model::Adapters::Sql::Collection] the filtered
101
+ # collection
102
+ #
103
+ # @api private
104
+ # @since 0.1.0
105
+ def or(*args)
106
+ Collection.new(super, @mapped_collection)
107
+ end
108
+
109
+ # Filters the current scope with an `order` directive.
110
+ #
111
+ # @param args [Array] the array of arguments
112
+ #
113
+ # @see Hanami::Model::Adapters::Sql::Query#order
114
+ #
115
+ # @return [Hanami::Model::Adapters::Sql::Collection] the filtered
116
+ # collection
117
+ #
118
+ # @api private
119
+ # @since 0.1.0
120
+ def order(*args)
121
+ Collection.new(super, @mapped_collection)
122
+ end
123
+
124
+ # Filters the current scope with an `order` directive.
125
+ #
126
+ # @param args [Array] the array of arguments
127
+ #
128
+ # @see Hanami::Model::Adapters::Sql::Query#order
129
+ #
130
+ # @return [Hanami::Model::Adapters::Sql::Collection] the filtered
131
+ # collection
132
+ #
133
+ # @api private
134
+ # @since 0.1.0
135
+ def order_more(*args)
136
+ Collection.new(super, @mapped_collection)
137
+ end
138
+
139
+ # Filters the current scope with a `select` directive.
140
+ #
141
+ # @param args [Array] the array of arguments
142
+ #
143
+ # @see Hanami::Model::Adapters::Sql::Query#select
144
+ #
145
+ # @return [Hanami::Model::Adapters::Sql::Collection] the filtered
146
+ # collection
147
+ #
148
+ # @api private
149
+ # @since 0.1.0
150
+ if RUBY_VERSION >= '2.1'
151
+ def select(*args)
152
+ Collection.new(super, @mapped_collection)
153
+ end
154
+ else
155
+ def select(*args)
156
+ Collection.new(__getobj__.select(*Hanami::Utils::Kernel.Array(args)), @mapped_collection)
157
+ end
158
+ end
159
+
160
+
161
+ # Filters the current scope with a `group` directive.
162
+ #
163
+ # @param args [Array] the array of arguments
164
+ #
165
+ # @see Hanami::Model::Adapters::Sql::Query#group
166
+ #
167
+ # @return [Hanami::Model::Adapters::Sql::Collection] the filtered
168
+ # collection
169
+ #
170
+ # @api private
171
+ # @since 0.5.0
172
+ def group(*args)
173
+ Collection.new(super, @mapped_collection)
174
+ end
175
+
176
+ # Filters the current scope with a `where` directive.
177
+ #
178
+ # @param args [Array] the array of arguments
179
+ #
180
+ # @see Hanami::Model::Adapters::Sql::Query#where
181
+ #
182
+ # @return [Hanami::Model::Adapters::Sql::Collection] the filtered
183
+ # collection
184
+ #
185
+ # @api private
186
+ # @since 0.1.0
187
+ def where(*args)
188
+ Collection.new(super, @mapped_collection)
189
+ end
190
+
191
+ # Updates the record corresponding to the given entity.
192
+ #
193
+ # @param entity [Object] the entity to persist
194
+ #
195
+ # @see Hanami::Model::Adapters::Sql::Command#update
196
+ #
197
+ # @api private
198
+ # @since 0.1.0
199
+ def update(entity)
200
+ serialized_entity = _serialize(entity)
201
+ super(serialized_entity)
202
+
203
+ _deserialize(serialized_entity)
204
+ end
205
+
206
+ # Resolves self by fetching the records from the database and
207
+ # translating them into entities.
208
+ #
209
+ # @return [Array] the result of the query
210
+ #
211
+ # @api private
212
+ # @since 0.1.0
213
+ def to_a
214
+ @mapped_collection.deserialize(self)
215
+ end
216
+
217
+ # Select all attributes for current scope
218
+ #
219
+ # @return [Hanami::Model::Adapters::Sql::Collection] the filtered
220
+ # collection
221
+ #
222
+ # @api private
223
+ # @since 0.5.0
224
+ #
225
+ # @see http://www.rubydoc.info/github/jeremyevans/sequel/Sequel%2FDataset%3Aselect_all
226
+ def select_all
227
+ Collection.new(super(table_name), @mapped_collection)
228
+ end
229
+
230
+ # Use join table for current scope
231
+ #
232
+ # @return [Hanami::Model::Adapters::Sql::Collection] the filtered
233
+ # collection
234
+ #
235
+ # @api private
236
+ # @since 0.5.0
237
+ #
238
+ # @see http://www.rubydoc.info/github/jeremyevans/sequel/Sequel%2FDataset%3Ajoin_table
239
+ def join_table(*args)
240
+ Collection.new(super, @mapped_collection)
241
+ end
242
+
243
+ # Return table name mapped collection
244
+ #
245
+ # @return [String] table name
246
+ #
247
+ # @api private
248
+ # @since 0.5.0
249
+ def table_name
250
+ @mapped_collection.name
251
+ end
252
+
253
+ # Name of the identity column in database
254
+ #
255
+ # @return [Symbol] the identity name
256
+ #
257
+ # @api private
258
+ # @since 0.5.0
259
+ def identity
260
+ @mapped_collection.identity
261
+ end
262
+
263
+ private
264
+ # Serialize the given entity before to persist in the database.
265
+ #
266
+ # @return [Hash] the serialized entity
267
+ #
268
+ # @api private
269
+ # @since 0.1.0
270
+ def _serialize(entity)
271
+ @mapped_collection.serialize(entity)
272
+ end
273
+
274
+ # Deserialize the given entity after it was persisted in the database.
275
+ #
276
+ # @return [Hanami::Entity] the deserialized entity
277
+ #
278
+ # @api private
279
+ # @since 0.2.2
280
+ def _deserialize(entity)
281
+ @mapped_collection.deserialize([entity]).first
282
+ end
283
+ end
284
+ end
285
+ end
286
+ end
287
+ end