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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +145 -0
- data/EXAMPLE.md +212 -0
- data/LICENSE.md +22 -0
- data/README.md +600 -7
- data/hanami-model.gemspec +17 -12
- data/lib/hanami-model.rb +1 -0
- data/lib/hanami/entity.rb +298 -0
- data/lib/hanami/entity/dirty_tracking.rb +74 -0
- data/lib/hanami/model.rb +204 -2
- data/lib/hanami/model/adapters/abstract.rb +281 -0
- data/lib/hanami/model/adapters/file_system_adapter.rb +288 -0
- data/lib/hanami/model/adapters/implementation.rb +111 -0
- data/lib/hanami/model/adapters/memory/collection.rb +132 -0
- data/lib/hanami/model/adapters/memory/command.rb +113 -0
- data/lib/hanami/model/adapters/memory/query.rb +653 -0
- data/lib/hanami/model/adapters/memory_adapter.rb +179 -0
- data/lib/hanami/model/adapters/null_adapter.rb +24 -0
- data/lib/hanami/model/adapters/sql/collection.rb +287 -0
- data/lib/hanami/model/adapters/sql/command.rb +73 -0
- data/lib/hanami/model/adapters/sql/console.rb +33 -0
- data/lib/hanami/model/adapters/sql/consoles/mysql.rb +49 -0
- data/lib/hanami/model/adapters/sql/consoles/postgresql.rb +48 -0
- data/lib/hanami/model/adapters/sql/consoles/sqlite.rb +26 -0
- data/lib/hanami/model/adapters/sql/query.rb +788 -0
- data/lib/hanami/model/adapters/sql_adapter.rb +296 -0
- data/lib/hanami/model/coercer.rb +74 -0
- data/lib/hanami/model/config/adapter.rb +116 -0
- data/lib/hanami/model/config/mapper.rb +45 -0
- data/lib/hanami/model/configuration.rb +275 -0
- data/lib/hanami/model/error.rb +7 -0
- data/lib/hanami/model/mapper.rb +124 -0
- data/lib/hanami/model/mapping.rb +48 -0
- data/lib/hanami/model/mapping/attribute.rb +85 -0
- data/lib/hanami/model/mapping/coercers.rb +314 -0
- data/lib/hanami/model/mapping/collection.rb +490 -0
- data/lib/hanami/model/mapping/collection_coercer.rb +79 -0
- data/lib/hanami/model/migrator.rb +324 -0
- data/lib/hanami/model/migrator/adapter.rb +170 -0
- data/lib/hanami/model/migrator/connection.rb +133 -0
- data/lib/hanami/model/migrator/mysql_adapter.rb +72 -0
- data/lib/hanami/model/migrator/postgres_adapter.rb +119 -0
- data/lib/hanami/model/migrator/sqlite_adapter.rb +110 -0
- data/lib/hanami/model/version.rb +4 -1
- data/lib/hanami/repository.rb +872 -0
- metadata +100 -16
- data/.gitignore +0 -9
- data/Gemfile +0 -4
- data/Rakefile +0 -2
- data/bin/console +0 -14
- 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
|