# Repositories can be used in Popcorn app to manage your data persistence.
# Here an example with a book management app:
#
-# ~~~
+# ~~~nitish
# # First we declare the `Book` class. It has to be serializable so it can be used
# # within a `Repository`.
#
# import popcorn
# import popcorn::pop_repos
+# import popcorn::pop_json
#
# # Serializable book representation.
# class Book
# serialize
-# super Jsonable
#
# # Book uniq ID
# var id: String = (new MongoObjectId).id is serialize_as "_id"
# redef fun to_s do return title
# redef fun ==(o) do return o isa SELF and id == o.id
# redef fun hash do return id.hash
-# redef fun to_json do return serialize_to_json
# end
#
# # We then need to subclass the `MongoRepository` to provide Book specific services.
# ~~~
module pop_repos
+import popcorn::pop_config
import serialization
-import json::serialization
-import mongodb
+import json
+import mongodb::queries
+
+redef class AppConfig
+
+ # Default database host string for MongoDb
+ var default_db_host = "mongodb://localhost:27017/"
+
+ # Default database hostname
+ var default_db_name = "popcorn"
+
+ # MongoDb host name
+ var opt_db_host = new OptionString("MongoDb host", "--db-host")
+
+ # MongoDb database name
+ var opt_db_name = new OptionString("MongoDb database name", "--db-name")
+
+ # MongoDB server used for data persistence
+ fun db_host: String do return opt_db_host.value or else ini["db.host"] or else default_db_host
+
+ # MongoDB DB used for data persistence
+ fun db_name: String do return opt_db_name.value or else ini["db.name"] or else default_db_name
+
+ init do
+ super
+ add_option(opt_db_host, opt_db_name)
+ end
+
+ # Mongo db client
+ var client = new MongoClient(db_host) is lazy
+
+ # Mongo db instance
+ var db: MongoDb = client.database(db_name) is lazy
+end
# A Repository is an object that can store serialized instances.
#
# Find all instances based on `query`
#
# Using `query` == null will retrieve all the document in the repository.
- fun find_all(query: nullable QUERY): Array[E] is abstract
+ fun find_all(query: nullable QUERY, skip, limit: nullable Int): Array[E] is abstract
+
+ # Count instances that matches `query`
+ fun count(query: nullable QUERY): Int is abstract
# Save an `instance`
fun save(instance: E): Bool is abstract
# Remove the instance based on `query`
fun remove(query: nullable QUERY): Bool is abstract
+ # Remove all the instances matching on `query`
+ fun remove_all(query: nullable QUERY): Bool is abstract
+
# Remove all instances
fun clear: Bool is abstract
# Serialization from/to Json is used to translate from/to nit instances.
#
# See `MongoRepository` for a concrete implementation example.
-interface JsonRepository[E: Serializable]
+abstract class JsonRepository[E: Serializable]
super Repository[E]
redef fun serialize(item) do
if item == null then return null
- return item.serialize_to_json
+ var stream = new StringWriter
+ var serializer = new RepoSerializer(stream)
+ serializer.serialize item
+ stream.close
+ return stream.to_s
end
redef fun deserialize(string) do
end
end
+private class RepoSerializer
+ super JsonSerializer
+
+ # Remove caching when saving refs to db
+ redef fun serialize_reference(object) do serialize object
+end
+
# A Repository that uses MongoDB as backend.
#
-# ~~~
+# ~~~nitish
# import popcorn
-# import popcorn::pop_routes
+# import popcorn::pop_repos
+# import popcorn::pop_json
#
# # First, let's create a User abstraction:
#
# # Serializable user representation.
# class User
+# super RepoObject
# serialize
-# super Jsonable
-#
-# # User uniq ID
-# var id: String = (new MongoObjectId).id is serialize_as "_id"
#
# # User login
# var login: String
# var password: String is writable
#
# redef fun to_s do return login
-# redef fun ==(o) do return o isa SELF and id == o.id
-# redef fun hash do return id.hash
-# redef fun to_json do return serialize_to_json
# end
#
# # We then need to subclass the `MongoRepository` to provide User specific services:
return deserialize(res.to_json)
end
- redef fun find_all(query) do
+ redef fun find_all(query, skip, limit) do
var res = new Array[E]
- for e in collection.find_all(query or else new JsonObject) do
+ for e in collection.find_all(query or else new JsonObject, skip, limit) do
res.add deserialize(e.to_json).as(E)
end
return res
end
+ redef fun count(query) do
+ return collection.count(query or else new JsonObject)
+ end
+
redef fun save(item) do
var json = serialize(item).as(String)
var obj = json.parse_json.as(JsonObject)
return collection.remove(query or else new JsonObject)
end
+ redef fun remove_all(query) do
+ return collection.remove_all(query or else new JsonObject)
+ end
+
redef fun clear do return collection.drop
+
+ # Perform an aggregation query over the repo.
+ fun aggregate(pipeline: JsonArray): Array[E] do
+ var res = new Array[E]
+ for obj in collection.aggregate(pipeline) do
+ var instance = deserialize(obj.to_json)
+ if instance == null then continue
+ res.add instance
+ end
+ return res
+ end
+end
+
+# Base serializable entity that can go into a JsonRepository
+#
+# Provide boiler plate implementation of all object serializable to json.
+#
+# `id` is used as a primary key for `find_by_id`.
+#
+# Subclassing RepoObject makes it easy to create a serializable class:
+# ~~~
+# import popcorn::pop_repos
+#
+# class Album
+# super RepoObject
+# serialize
+#
+# var title: String
+# var price: Float
+# end
+# ~~~
+#
+# Do not forget the `serialize` annotation else the fields will not be serialized.
+#
+# It is also possible to redefine the `id` primary key to use your own:
+# ~~~
+# import popcorn::pop_repos
+#
+# class Order
+# super RepoObject
+# serialize
+#
+# redef var id = "order-{get_time}"
+#
+# # ...
+#
+# end
+# ~~~
+abstract class RepoObject
+ serialize
+
+ # `self` unique id.
+ #
+ # This attribute is serialized under the key `_id` to be used
+ # as primary key by MongoDb
+ var id: String = (new MongoObjectId).id is writable, serialize_as "_id"
+
+ # Base object comparison on ID
+ #
+ # Because multiple deserialization can exists of the same instance,
+ # we use the ID to determine if two object are the same.
+ redef fun ==(o) do return o isa SELF and id == o.id
+
+ redef fun hash do return id.hash
+ redef fun to_s do return id
end
# JsonObject can be used as a `RepositoryQuery`.