1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2016 Alexandre Terrasa <alexandre@moz-code.org>
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 # Repositories for data management.
19 # Repositories are used to apply persistence on instances (or **documents**).
20 # Using repositories one can store and retrieve instance in a clean and maintenable
23 # This module provides the base interface `Repository` that defines the persistence
24 # services available in all kind of repos.
25 # `JsonRepository` factorizes all repositories dedicated to Json data or objects
26 # serializable to Json.
28 # `MongoRepository` is provided as a concrete example of repository.
29 # It implements all the services from `Repository` using a Mongo database as backend.
31 # Repositories can be used in Popcorn app to manage your data persistence.
32 # Here an example with a book management app:
35 # # First we declare the `Book` class. It has to be serializable so it can be used
36 # # within a `Repository`.
39 # import popcorn::pop_repos
41 # # Serializable book representation.
47 # var id: String = (new MongoObjectId).id is serialize_as "_id"
54 # redef fun to_s do return title
55 # redef fun ==(o) do return o isa SELF and id == o.id
56 # redef fun hash do return id.hash
57 # redef fun to_json do return serialize_to_json
60 # # We then need to subclass the `MongoRepository` to provide Book specific services.
62 # # Book repository for Mongo
64 # super MongoRepository[Book]
66 # # Find books by title
67 # fun find_by_title(title: String): Array[Book] do
68 # var q = new JsonObject
74 # # The repository can be used in a Handler to manage book in a REST API.
81 # # Return a json array of all books
83 # # If the get parameters `title` is provided, returns a json array of books
84 # # matching the `title`.
85 # redef fun get(req, res) do
86 # var title = req.string_arg("title")
87 # if title == null then
88 # res.json new JsonArray.from(repo.find_all)
90 # res.json new JsonArray.from(repo.find_by_title(title))
95 # redef fun post(req, res) do
96 # var title = req.string_arg("title")
97 # if title == null then
101 # var book = new Book(title)
107 # # Let's wrap it all together in a Popcorn app:
110 # var mongo = new MongoClient("mongodb://localhost:27017/")
111 # var db = mongo.database("tests_app_{100000.rand}")
112 # var coll = db.collection("books")
116 # var repo = new BookRepo(coll)
117 # app.use("/books", new BookHandler(repo))
118 # app.listen("localhost", 3000)
123 import json
::serialization
126 # A Repository is an object that can store serialized instances.
128 # Repository is the base class of all kind of persistance processes. It offers
129 # the base CRUD services to save (add/update), find and delete instances.
131 # Instances are stored in their serialized form. See the `serialization` package
132 # for more documentation.
133 interface Repository[E
: Serializable]
135 # Kind of queries accepted
137 # Can be redefined to accept more precise queries depending on the backend used.
138 type QUERY: RepositoryQuery
140 # Find an instance by it's `id`
142 # `id` is an abstract thing at this stage
143 # TODO maybe introduce the `PrimaryKey` concept?
144 fun find_by_id
(id
: String): nullable E
is abstract
146 # Find an instance based on `query`
147 fun find
(query
: QUERY): nullable E
is abstract
149 # Find all instances based on `query`
151 # Using `query` == null will retrieve all the document in the repository.
152 fun find_all
(query
: nullable QUERY): Array[E
] is abstract
155 fun save
(instance
: E
): Bool is abstract
157 # Remove the instance with `id`
158 fun remove_by_id
(id
: String): Bool is abstract
160 # Remove the instance based on `query`
161 fun remove
(query
: nullable QUERY): Bool is abstract
163 # Remove all instances
164 fun clear
: Bool is abstract
166 # Serialize an `instance` to a String.
167 fun serialize
(instance
: nullable E
): nullable String is abstract
169 # Deserialize a `string` to an instance.
170 fun deserialize
(string
: nullable String): nullable E
is abstract
173 # An abstract Query representation.
175 # Since the kind of query available depends on the database backend choice or
176 # implementation, this interface is used to provide a common type to all the
179 # Redefine `Repository::QUERY` to use your own kind of query.
180 interface RepositoryQuery end
182 # A Repository for JsonObjects.
184 # As for document oriented databases, Repository can be used to store and retrieve
186 # Serialization from/to Json is used to translate from/to nit instances.
188 # See `MongoRepository` for a concrete implementation example.
189 interface JsonRepository[E
: Serializable]
192 redef fun serialize
(item
) do
193 if item
== null then return null
194 return item
.serialize_to_json
197 redef fun deserialize
(string
) do
198 if string
== null then return null
199 var deserializer
= new JsonDeserializer(string
)
200 return deserializer
.deserialize
.as(E
)
204 # A Repository that uses MongoDB as backend.
208 # import popcorn::pop_routes
210 # # First, let's create a User abstraction:
212 # # Serializable user representation.
218 # var id: String = (new MongoObjectId).id is serialize_as "_id"
224 # var password: String is writable
226 # redef fun to_s do return login
227 # redef fun ==(o) do return o isa SELF and id == o.id
228 # redef fun hash do return id.hash
229 # redef fun to_json do return serialize_to_json
232 # # We then need to subclass the `MongoRepository` to provide User specific services:
234 # # User repository for Mongo
236 # super MongoRepository[User]
238 # # Find a user by its login
239 # fun find_by_login(login: String): nullable User do
240 # var q = new JsonObject
246 # # The repository can then be used with User instances:
249 # var mongo = new MongoClient("mongodb://localhost:27017/")
250 # var db = mongo.database("tests")
251 # var coll = db.collection("test_pop_repo_{100000.rand}")
253 # # Create a user repo to store User instances
254 # var repo = new UserRepo(coll)
256 # # Create some users
257 # repo.save(new User("Morriar", "1234"))
258 # repo.save(new User("Alex", "password"))
260 # assert repo.find_all.length == 2
261 # assert repo.find_by_login("Morriar").password == "1234"
263 # assert repo.find_all.length == 0
265 class MongoRepository[E
: Serializable]
266 super JsonRepository[E
]
268 redef type QUERY: JsonObject
270 # MongoDB collection used to store objects
271 var collection
: MongoCollection
273 redef fun find_by_id
(id
) do
274 var query
= new JsonObject
279 redef fun find
(query
) do
280 var res
= collection
.find
(query
)
281 if res
== null then return null
282 return deserialize
(res
.to_json
)
285 redef fun find_all
(query
) do
286 var res
= new Array[E
]
287 for e
in collection
.find_all
(query
or else new JsonObject) do
288 res
.add deserialize
(e
.to_json
).as(E
)
293 redef fun save
(item
) do
294 var json
= serialize
(item
).as(String)
295 var obj
= json
.parse_json
.as(JsonObject)
296 return collection
.save
(obj
)
299 redef fun remove_by_id
(id
) do
300 var query
= new JsonObject
305 redef fun remove
(query
) do
306 return collection
.remove
(query
or else new JsonObject)
309 redef fun clear
do return collection
.drop
312 # JsonObject can be used as a `RepositoryQuery`.
315 redef class JsonObject
316 super RepositoryQuery