lotus-model 0.1.2 → 0.2.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 +52 -168
- data/EXAMPLE.md +35 -40
- data/README.md +152 -12
- data/lib/lotus/entity.rb +107 -24
- data/lib/lotus/model.rb +169 -8
- data/lib/lotus/model/adapters/abstract.rb +3 -2
- data/lib/lotus/model/adapters/file_system_adapter.rb +272 -0
- data/lib/lotus/model/adapters/implementation.rb +1 -1
- data/lib/lotus/model/adapters/memory/command.rb +2 -1
- data/lib/lotus/model/adapters/memory/query.rb +49 -10
- data/lib/lotus/model/adapters/memory_adapter.rb +13 -5
- data/lib/lotus/model/adapters/null_adapter.rb +20 -0
- data/lib/lotus/model/adapters/sql/collection.rb +18 -18
- data/lib/lotus/model/adapters/sql/query.rb +23 -3
- data/lib/lotus/model/config/adapter.rb +108 -0
- data/lib/lotus/model/config/mapper.rb +45 -0
- data/lib/lotus/model/configuration.rb +187 -0
- data/lib/lotus/model/mapper.rb +26 -3
- data/lib/lotus/model/mapping.rb +24 -0
- data/lib/lotus/model/mapping/coercer.rb +3 -3
- data/lib/lotus/model/mapping/coercions.rb +30 -0
- data/lib/lotus/model/mapping/collection.rb +127 -9
- data/lib/lotus/model/version.rb +1 -1
- data/lib/lotus/repository.rb +19 -11
- data/lotus-model.gemspec +2 -1
- metadata +16 -5
data/lib/lotus/entity.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
require 'lotus/utils/kernel'
|
2
|
+
require 'lotus/utils/attributes'
|
2
3
|
|
3
4
|
module Lotus
|
4
5
|
# An object that is defined by its identity.
|
5
|
-
# See Domain Driven Design by Eric Evans.
|
6
|
+
# See "Domain Driven Design" by Eric Evans.
|
6
7
|
#
|
7
8
|
# An entity is the core of an application, where the part of the domain
|
8
|
-
# logic is implemented. It's a small, cohesive object that
|
9
|
+
# logic is implemented. It's a small, cohesive object that expresses coherent
|
9
10
|
# and meaningful behaviors.
|
10
11
|
#
|
11
12
|
# It deals with one and only one responsibility that is pertinent to the
|
@@ -21,18 +22,18 @@ module Lotus
|
|
21
22
|
#
|
22
23
|
# class Person
|
23
24
|
# include Lotus::Entity
|
24
|
-
#
|
25
|
+
# attributes :name, :age
|
25
26
|
# end
|
26
27
|
#
|
27
|
-
# When a class includes `Lotus::Entity`
|
28
|
-
# By then calling the `.attributes=` class method, the following methods are
|
29
|
-
# added:
|
28
|
+
# When a class includes `Lotus::Entity` it receives the following interface:
|
30
29
|
#
|
31
30
|
# * #id
|
32
31
|
# * #id=
|
33
32
|
# * #initialize(attributes = {})
|
34
33
|
#
|
35
|
-
#
|
34
|
+
# `Lotus::Entity` also provides the `.attributes=` for defining attribute accessors for the given names.
|
35
|
+
#
|
36
|
+
# If we expand the code above in **pure Ruby**, it would be:
|
36
37
|
#
|
37
38
|
# @example Pure Ruby
|
38
39
|
# class Person
|
@@ -43,11 +44,18 @@ module Lotus
|
|
43
44
|
# end
|
44
45
|
# end
|
45
46
|
#
|
46
|
-
#
|
47
|
-
#
|
47
|
+
# **Lotus::Model** ships `Lotus::Entity` for developers's convenience.
|
48
|
+
#
|
49
|
+
# **Lotus::Model** depends on a narrow and well-defined interface for an
|
50
|
+
# Entity - `#id`, `#id=`, `#initialize(attributes={})`.If your object
|
51
|
+
# implements that interface then that object can be used as an Entity in the
|
52
|
+
# **Lotus::Model** framework.
|
53
|
+
#
|
54
|
+
# However, we suggest to implement this interface by including
|
55
|
+
# `Lotus::Entity`, in case that future versions of the framework will expand
|
56
|
+
# it.
|
48
57
|
#
|
49
|
-
#
|
50
|
-
# in case that future versions of the framework will expand it.
|
58
|
+
# See Dependency Inversion Principle for more on interfaces.
|
51
59
|
#
|
52
60
|
# @since 0.1.0
|
53
61
|
#
|
@@ -88,9 +96,9 @@ module Lotus
|
|
88
96
|
# Please notice that the required `id` attribute is automatically defined
|
89
97
|
# and can be omitted in the arguments.
|
90
98
|
#
|
91
|
-
# @param
|
99
|
+
# @param attrs [Array<Symbol>] a set of arbitrary attribute names
|
92
100
|
#
|
93
|
-
# @since 0.
|
101
|
+
# @since 0.2.0
|
94
102
|
#
|
95
103
|
# @see Lotus::Repository
|
96
104
|
# @see Lotus::Model::Mapper
|
@@ -100,22 +108,59 @@ module Lotus
|
|
100
108
|
#
|
101
109
|
# class User
|
102
110
|
# include Lotus::Entity
|
103
|
-
#
|
111
|
+
# attributes :name, :age
|
112
|
+
# end
|
113
|
+
# User.attributes => #<Set: {:id, :name, :age}>
|
114
|
+
#
|
115
|
+
# @example Given params is array of attributes
|
116
|
+
# require 'lotus/model'
|
117
|
+
#
|
118
|
+
# class User
|
119
|
+
# include Lotus::Entity
|
120
|
+
# attributes [:name, :age]
|
121
|
+
# end
|
122
|
+
# User.attributes => #<Set: {:id, :name, :age}>
|
123
|
+
#
|
124
|
+
# @example Extend entity
|
125
|
+
# require 'lotus/model'
|
126
|
+
#
|
127
|
+
# class User
|
128
|
+
# include Lotus::Entity
|
129
|
+
# attributes :name
|
104
130
|
# end
|
105
|
-
|
106
|
-
|
131
|
+
#
|
132
|
+
# class DeletedUser < User
|
133
|
+
# include Lotus::Entity
|
134
|
+
# attributes :deleted_at
|
135
|
+
# end
|
136
|
+
#
|
137
|
+
# User.attributes => #<Set: {:id, :name}>
|
138
|
+
# DeletedUser.attributes => #<Set: {:id, :name, :deleted_at}>
|
139
|
+
def attributes(*attrs)
|
140
|
+
if attrs.any?
|
141
|
+
self.attributes.merge Lotus::Utils::Kernel.Array(attrs)
|
107
142
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
143
|
+
class_eval <<-END_EVAL, __FILE__, __LINE__
|
144
|
+
def initialize(attributes = {})
|
145
|
+
attributes = Lotus::Utils::Attributes.new(attributes)
|
146
|
+
#{@attributes.map do |a|
|
147
|
+
"@#{a} = attributes.get(:#{a})"
|
148
|
+
end.join("\n") }
|
149
|
+
end
|
150
|
+
END_EVAL
|
113
151
|
|
114
|
-
|
152
|
+
attr_accessor *@attributes
|
153
|
+
else
|
154
|
+
@attributes ||= Set.new([:id])
|
155
|
+
end
|
115
156
|
end
|
116
157
|
|
117
|
-
|
118
|
-
|
158
|
+
protected
|
159
|
+
|
160
|
+
# @see Class#inherited
|
161
|
+
def inherited(subclass)
|
162
|
+
subclass.attributes *attributes
|
163
|
+
super
|
119
164
|
end
|
120
165
|
end
|
121
166
|
|
@@ -146,6 +191,44 @@ module Lotus
|
|
146
191
|
self.class == other.class &&
|
147
192
|
self.id == other.id
|
148
193
|
end
|
194
|
+
|
195
|
+
# Return the hash of attributes
|
196
|
+
#
|
197
|
+
# @since 0.2.0
|
198
|
+
#
|
199
|
+
# @example
|
200
|
+
# require 'lotus/model'
|
201
|
+
# class User
|
202
|
+
# include Lotus::Entity
|
203
|
+
# attributes :name
|
204
|
+
# end
|
205
|
+
#
|
206
|
+
# user = User.new(id: 23, name: 'Luca')
|
207
|
+
# user.to_h # => { :id => 23, :name => "Luca" }
|
208
|
+
def to_h
|
209
|
+
Hash[self.class.attributes.map { |a| [a, public_send(a)] }]
|
210
|
+
end
|
211
|
+
|
212
|
+
# Set attributes for entity
|
213
|
+
#
|
214
|
+
# @since 0.2.0
|
215
|
+
#
|
216
|
+
# @example
|
217
|
+
# require 'lotus/model'
|
218
|
+
# class User
|
219
|
+
# include Lotus::Entity
|
220
|
+
# attributes :name
|
221
|
+
# end
|
222
|
+
#
|
223
|
+
# user = User.new(name: 'Lucca')
|
224
|
+
# user.update(name: 'Luca')
|
225
|
+
# user.name # => 'Luca'
|
226
|
+
def update(attributes={})
|
227
|
+
attributes.each do |attribute, value|
|
228
|
+
public_send("#{attribute}=", value)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
149
232
|
end
|
150
233
|
end
|
151
234
|
|
data/lib/lotus/model.rb
CHANGED
@@ -2,20 +2,13 @@ require 'lotus/model/version'
|
|
2
2
|
require 'lotus/entity'
|
3
3
|
require 'lotus/repository'
|
4
4
|
require 'lotus/model/mapper'
|
5
|
+
require 'lotus/model/configuration'
|
5
6
|
|
6
7
|
module Lotus
|
7
8
|
# Model
|
8
9
|
#
|
9
10
|
# @since 0.1.0
|
10
11
|
module Model
|
11
|
-
# Error for not found entity
|
12
|
-
#
|
13
|
-
# @since 0.1.0
|
14
|
-
#
|
15
|
-
# @see Lotus::Repository.find
|
16
|
-
class EntityNotFound < ::StandardError
|
17
|
-
end
|
18
|
-
|
19
12
|
# Error for non persisted entity
|
20
13
|
# It's raised when we try to update or delete a non persisted entity.
|
21
14
|
#
|
@@ -24,5 +17,173 @@ module Lotus
|
|
24
17
|
# @see Lotus::Repository.update
|
25
18
|
class NonPersistedEntityError < ::StandardError
|
26
19
|
end
|
20
|
+
|
21
|
+
# Error for invalid mapper configuration
|
22
|
+
# It's raised when mapping is not configured correctly
|
23
|
+
#
|
24
|
+
# @since 0.2.0
|
25
|
+
#
|
26
|
+
# @see Lotus::Configuration#mapping
|
27
|
+
class InvalidMappingError < ::StandardError
|
28
|
+
end
|
29
|
+
|
30
|
+
include Utils::ClassAttribute
|
31
|
+
|
32
|
+
# Framework configuration
|
33
|
+
#
|
34
|
+
# @since 0.2.0
|
35
|
+
# @api private
|
36
|
+
class_attribute :configuration
|
37
|
+
self.configuration = Configuration.new
|
38
|
+
|
39
|
+
# Configure the framework.
|
40
|
+
# It yields the given block in the context of the configuration
|
41
|
+
#
|
42
|
+
# @param blk [Proc] the configuration block
|
43
|
+
#
|
44
|
+
# @since 0.2.0
|
45
|
+
#
|
46
|
+
# @see Lotus::Model
|
47
|
+
#
|
48
|
+
# @example
|
49
|
+
# require 'lotus/model'
|
50
|
+
#
|
51
|
+
# Lotus::Model.configure do
|
52
|
+
# adapter type: :sql, uri: 'postgres://localhost/database'
|
53
|
+
#
|
54
|
+
# mapping do
|
55
|
+
# collection :users do
|
56
|
+
# entity User
|
57
|
+
#
|
58
|
+
# attribute :id, Integer
|
59
|
+
# attribute :name, String
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# Adapter MUST follow the convention in which adapter class is inflection of adapter name
|
65
|
+
# The above example has name :sql, thus derived class will be `Lotus::Model::Adapters::SqlAdapter`
|
66
|
+
def self.configure(&blk)
|
67
|
+
configuration.instance_eval(&blk)
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
# Load the framework
|
72
|
+
#
|
73
|
+
# @since 0.2.0
|
74
|
+
# @api private
|
75
|
+
def self.load!
|
76
|
+
configuration.load!
|
77
|
+
end
|
78
|
+
|
79
|
+
# Unload the framework
|
80
|
+
#
|
81
|
+
# @since 0.2.0
|
82
|
+
# @api private
|
83
|
+
def self.unload!
|
84
|
+
configuration.unload!
|
85
|
+
end
|
86
|
+
|
87
|
+
# Duplicate Lotus::Model in order to create a new separated instance
|
88
|
+
# of the framework.
|
89
|
+
#
|
90
|
+
# The new instance of the framework will be completely decoupled from the
|
91
|
+
# original. It will inherit the configuration, but all the changes that
|
92
|
+
# happen after the duplication, won't be reflected on the other copies.
|
93
|
+
#
|
94
|
+
# @return [Module] a copy of Lotus::Model
|
95
|
+
#
|
96
|
+
# @since 0.2.0
|
97
|
+
# @api private
|
98
|
+
#
|
99
|
+
# @example Basic usage
|
100
|
+
# require 'lotus/model'
|
101
|
+
#
|
102
|
+
# module MyApp
|
103
|
+
# Model = Lotus::Model.dupe
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# MyApp::Model == Lotus::Model # => false
|
107
|
+
#
|
108
|
+
# MyApp::Model.configuration ==
|
109
|
+
# Lotus::Model.configuration # => false
|
110
|
+
#
|
111
|
+
# @example Inheriting configuration
|
112
|
+
# require 'lotus/model'
|
113
|
+
#
|
114
|
+
# Lotus::Model.configure do
|
115
|
+
# adapter type: :sql, uri: 'sqlite3://uri'
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
# module MyApp
|
119
|
+
# Model = Lotus::Model.dupe
|
120
|
+
# end
|
121
|
+
#
|
122
|
+
# module MyApi
|
123
|
+
# Model = Lotus::Model.dupe
|
124
|
+
# Model.configure do
|
125
|
+
# adapter type: :sql, uri: 'postgresql://uri'
|
126
|
+
# end
|
127
|
+
# end
|
128
|
+
#
|
129
|
+
# Lotus::Model.configuration.adapter_config.uri # => 'sqlite3://uri'
|
130
|
+
# MyApp::Model.configuration.adapter_config.uri # => 'sqlite3://uri'
|
131
|
+
# MyApi::Model.configuration.adapter_config.uri # => 'postgresql://uri'
|
132
|
+
def self.dupe
|
133
|
+
dup.tap do |duplicated|
|
134
|
+
duplicated.configuration = configuration.duplicate
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Duplicate the framework and generate modules for the target application
|
139
|
+
#
|
140
|
+
# @param mod [Module] the Ruby namespace of the application
|
141
|
+
# @param blk [Proc] an optional block to configure the framework
|
142
|
+
#
|
143
|
+
# @return [Module] a copy of Lotus::Model
|
144
|
+
#
|
145
|
+
# @since 0.2.0
|
146
|
+
#
|
147
|
+
# @see Lotus::Model#dupe
|
148
|
+
# @see Lotus::Model::Configuration
|
149
|
+
#
|
150
|
+
# @example Basic usage
|
151
|
+
# require 'lotus/model'
|
152
|
+
#
|
153
|
+
# module MyApp
|
154
|
+
# Model = Lotus::Model.dupe
|
155
|
+
# end
|
156
|
+
#
|
157
|
+
# # It will:
|
158
|
+
# #
|
159
|
+
# # 1. Generate MyApp::Model
|
160
|
+
# # 2. Generate MyApp::Entity
|
161
|
+
# # 3. Generate MyApp::Repository
|
162
|
+
#
|
163
|
+
# MyApp::Model == Lotus::Model # => false
|
164
|
+
# MyApp::Repository == Lotus::Repository # => false
|
165
|
+
#
|
166
|
+
# @example Block usage
|
167
|
+
# require 'lotus/model'
|
168
|
+
#
|
169
|
+
# module MyApp
|
170
|
+
# Model = Lotus::Model.duplicate(self) do
|
171
|
+
# adapter type: :memory, uri: 'memory://localhost'
|
172
|
+
# end
|
173
|
+
# end
|
174
|
+
#
|
175
|
+
# Lotus::Model.configuration.adapter_config # => nil
|
176
|
+
# MyApp::Model.configuration.adapter_config # => #<Lotus::Model::Config::Adapter:0x007ff0ff0244f8 @type=:memory, @uri="memory://localhost", @class_name="MemoryAdapter">
|
177
|
+
def self.duplicate(mod, &blk)
|
178
|
+
dupe.tap do |duplicated|
|
179
|
+
mod.module_eval %{
|
180
|
+
Entity = Lotus::Entity.dup
|
181
|
+
Repository = Lotus::Repository.dup
|
182
|
+
}
|
183
|
+
|
184
|
+
duplicated.configure(&blk) if block_given?
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
27
188
|
end
|
28
189
|
end
|
@@ -35,7 +35,8 @@ module Lotus
|
|
35
35
|
#
|
36
36
|
# @since 0.1.0
|
37
37
|
def initialize(mapper, uri = nil)
|
38
|
-
@mapper
|
38
|
+
@mapper = mapper
|
39
|
+
@uri = uri
|
39
40
|
end
|
40
41
|
|
41
42
|
# Creates or updates a record in the database for the given entity.
|
@@ -96,7 +97,7 @@ module Lotus
|
|
96
97
|
raise NotImplementedError
|
97
98
|
end
|
98
99
|
|
99
|
-
# Returns
|
100
|
+
# Returns a unique record from the given collection, with the given
|
100
101
|
# identity.
|
101
102
|
#
|
102
103
|
# @param collection [Symbol] the target collection (it must be mapped).
|
@@ -0,0 +1,272 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'pathname'
|
3
|
+
require 'lotus/model/adapters/memory_adapter'
|
4
|
+
|
5
|
+
module Lotus
|
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 Lotus::Model::Adapters::Implementation
|
26
|
+
# @see Lotus::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
|
+
#
|
63
|
+
# @return [Lotus::Model::Adapters::FileSystemAdapter]
|
64
|
+
#
|
65
|
+
# @see Lotus::Model::Mapper
|
66
|
+
#
|
67
|
+
# @api private
|
68
|
+
# @since 0.2.0
|
69
|
+
def initialize(mapper, uri)
|
70
|
+
super
|
71
|
+
prepare(uri)
|
72
|
+
|
73
|
+
@_mutex = Mutex.new
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns all the records for the given collection
|
77
|
+
#
|
78
|
+
# @param collection [Symbol] the target collection (it must be mapped).
|
79
|
+
#
|
80
|
+
# @return [Array] all the records
|
81
|
+
#
|
82
|
+
# @api private
|
83
|
+
# @since 0.2.0
|
84
|
+
def all(collection)
|
85
|
+
_synchronize do
|
86
|
+
read(collection)
|
87
|
+
super
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns a unique record from the given collection, with the given
|
92
|
+
# id.
|
93
|
+
#
|
94
|
+
# @param collection [Symbol] the target collection (it must be mapped).
|
95
|
+
# @param id [Object] the identity of the object.
|
96
|
+
#
|
97
|
+
# @return [Object] the entity
|
98
|
+
#
|
99
|
+
# @api private
|
100
|
+
# @since 0.2.0
|
101
|
+
def find(collection, id)
|
102
|
+
_synchronize do
|
103
|
+
read(collection)
|
104
|
+
super
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns the first record in the given collection.
|
109
|
+
#
|
110
|
+
# @param collection [Symbol] the target collection (it must be mapped).
|
111
|
+
#
|
112
|
+
# @return [Object] the first entity
|
113
|
+
#
|
114
|
+
# @api private
|
115
|
+
# @since 0.2.0
|
116
|
+
def first(collection)
|
117
|
+
_synchronize do
|
118
|
+
read(collection)
|
119
|
+
super
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Returns the last record in the given collection.
|
124
|
+
#
|
125
|
+
# @param collection [Symbol] the target collection (it must be mapped).
|
126
|
+
#
|
127
|
+
# @return [Object] the last entity
|
128
|
+
#
|
129
|
+
# @api private
|
130
|
+
# @since 0.2.0
|
131
|
+
def last(collection)
|
132
|
+
_synchronize do
|
133
|
+
read(collection)
|
134
|
+
super
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Creates a record in the database for the given entity.
|
139
|
+
# It assigns the `id` attribute, in case of success.
|
140
|
+
#
|
141
|
+
# @param collection [Symbol] the target collection (it must be mapped).
|
142
|
+
# @param entity [#id=] the entity to create
|
143
|
+
#
|
144
|
+
# @return [Object] the entity
|
145
|
+
#
|
146
|
+
# @api private
|
147
|
+
# @since 0.2.0
|
148
|
+
def create(collection, entity)
|
149
|
+
_synchronize do
|
150
|
+
super
|
151
|
+
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
|
167
|
+
write(collection)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Deletes a record in the database corresponding to the given entity.
|
172
|
+
#
|
173
|
+
# @param collection [Symbol] the target collection (it must be mapped).
|
174
|
+
# @param entity [#id] the entity to delete
|
175
|
+
#
|
176
|
+
# @api private
|
177
|
+
# @since 0.2.0
|
178
|
+
def delete(collection, entity)
|
179
|
+
_synchronize do
|
180
|
+
super
|
181
|
+
write(collection)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Deletes all the records from the given collection and resets the
|
186
|
+
# identity counter.
|
187
|
+
#
|
188
|
+
# @param collection [Symbol] the target collection (it must be mapped).
|
189
|
+
#
|
190
|
+
# @api private
|
191
|
+
# @since 0.2.0
|
192
|
+
def clear(collection)
|
193
|
+
_synchronize do
|
194
|
+
super
|
195
|
+
write(collection)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Fabricates a query
|
200
|
+
#
|
201
|
+
# @param collection [Symbol] the target collection (it must be mapped).
|
202
|
+
# @param blk [Proc] a block of code to be executed in the context of
|
203
|
+
# the query.
|
204
|
+
#
|
205
|
+
# @return [Lotus::Model::Adapters::Memory::Query]
|
206
|
+
#
|
207
|
+
# @see Lotus::Model::Adapters::Memory::Query
|
208
|
+
#
|
209
|
+
# @api private
|
210
|
+
# @since 0.2.0
|
211
|
+
def query(collection, context = nil, &blk)
|
212
|
+
# _synchronize do
|
213
|
+
read(collection)
|
214
|
+
super
|
215
|
+
# end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Database informations
|
219
|
+
#
|
220
|
+
# @return [Hash] per collection informations
|
221
|
+
#
|
222
|
+
# @api private
|
223
|
+
# @since 0.2.0
|
224
|
+
def info
|
225
|
+
@collections.each_with_object({}) do |(collection,_), result|
|
226
|
+
result[collection] = query(collection).count
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
private
|
231
|
+
# @api private
|
232
|
+
# @since 0.2.0
|
233
|
+
def prepare(uri)
|
234
|
+
@root = Pathname.new(uri.sub(FILE_SCHEME, ''))
|
235
|
+
@root.mkpath
|
236
|
+
end
|
237
|
+
|
238
|
+
# @api private
|
239
|
+
# @since 0.2.0
|
240
|
+
def _synchronize
|
241
|
+
@_mutex.synchronize { yield }
|
242
|
+
end
|
243
|
+
|
244
|
+
# @api private
|
245
|
+
# @since 0.2.0
|
246
|
+
def write(collection)
|
247
|
+
path = @root.join("#{ collection }")
|
248
|
+
path.open(WRITING_MODE, CHMOD) {|f| f.write _dump( @collections.fetch(collection) ) }
|
249
|
+
end
|
250
|
+
|
251
|
+
# @api private
|
252
|
+
# @since 0.2.0
|
253
|
+
def read(collection)
|
254
|
+
path = @root.join("#{ collection }")
|
255
|
+
@collections[collection] = _load(path.read) if path.exist?
|
256
|
+
end
|
257
|
+
|
258
|
+
# @api private
|
259
|
+
# @since 0.2.0
|
260
|
+
def _dump(contents)
|
261
|
+
Marshal.dump(contents)
|
262
|
+
end
|
263
|
+
|
264
|
+
# @api private
|
265
|
+
# @since 0.2.0
|
266
|
+
def _load(contents)
|
267
|
+
Marshal.load(contents)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|