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,281 @@
|
|
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
|
@@ -0,0 +1,288 @@
|
|
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
|