hanami-model 0.0.0 → 0.6.0

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