Warning! This documentation is a work in progress. Expect things to be out of date and not actually work according to instructions.
Models, Data Access, and Querying
Data access in Stallion has four parts
- The Model, which is a POJO/Javabean, implements io.stallion.dataAccess.Model. A model instance represents a single item in your database or file-store.
- The ModelController, which implements io.stallion.dataAccess.ModelController. This is the API via which you will normally access and maniuplate data, it has helper methods for looking up objects by ID, or unique key, or finding based on matching parameters, along with the basic operations of saving or deleting objects.
- The Stash, which inherits from io.stallion.dataAccess.Stash. A stash implementation can sync all or part of the object collection from the datastore into local memory for ultra-fast access.
- The Persister, which implements io.stallion.dataAccess.Persister, that actually communicates to and from the datastore, whether that be the file system, MySQL, or Postgres.
When you register a Model and ModelController, by default, it will use the LocalMemoryStash
which syncs all items into memory. You can use NoStash
to avoid syncing anything to memory. This option will still use a query cache by default.
By default, when you register a model, Stallion will use the DbPersister
if you have configured a database, or the JsonFilePersister
otherwise. If you are using some other file format, say for instance, you have a folder of HTML files or markdown files, you could use the TextFilePersister
. If you had a folder of .toml
files, you would use the TomlPersister
.
When you register, you choose a “bucket” name, which is a key by which the controller can be accessed via templates or via myContext.dataAccess.<bucketName>
in Javascript code. By default, the bucket name will be the table name for database backed models, or the folder name in the app-data
directory for file backed models. If you want to use a different table or folder, you can pass that to the registration.
An example model and controller:
// A simple model and controller
var userProfiles = stallion
.modelRegistration()
.columns({
userId: new LongCol({uniqueKey: true}),
slug: new StringCol({uniqueKey: true}),
loggedInWith: new StringCol({length: 40, nullable: false, defaultValue: 'email'}),
// Set to the current UTC time when creating a new instance
createdAt: new DateTimeCol({nowOnCreate: true}),
// Set to the current UTC time when updating a an instance;
updatedAt: new DateTimeCol({nowOnUpdate: true})
})
.bucket('user_profiles')
.register();
// A model with lots of options
var events = stallion
.modelRegistration()
.columns({
creatorId: new LongCol({alternativeKey: true}),
createdAt: new DateTimeCol({nowOnCreate: true}),
updatedAt: new DateTimeCol({nowOnUpdate: true}),
privacy: new EnumCol({choices: ['semi-private']}),
key: new StringCol({uniqueKey: true}),
emailKey: new StringCol({uniqueKey: true}),
emailBody: new StringCol(),
emailSubject: new StringCol(),
state: new EnumCol({choices: ['feelers', 'definite', 'proposed']}),
maxNumber: new IntegerCol(),
canceled: new BooleanCol(),
canceledAt: new DateTimeCol(),
cancellationMessage: new StringCol(),
clubInvites: new ListCol(),
emailInvites: new ListCol(),
joinable: new FunctionCol(function(event) {
if (event.state === 'proposed' || event.state === 'definite') {
return true;
} else {
return false;
}
}),
url: new FunctionCol(function(event) {
return myContext.site.url + '/activity/' + event.key + '/' + event.slug;
}),
slug: new FunctionCol(function(event) {
var slug = stallion.Literals.truncateSmart(event.emailSubject, 30);
slug = stallion.GeneralUtils.slugify(slug);
return slug;
})
})
.controllerExtensions({
onPreCreatePrepare: function(o) {
if (!o.id) {
o.id = Packages.io.stallion.dataAccess.db.DB.instance().tickets.nextId();
}
if (!o.emailKey) {
o.emailKey = stallion.GeneralUtils.tokenForId(o.id, 8);
}
if (!o.key) {
o.key = stallion.GeneralUtils.tokenForId(o.id, 8);
}
}
})
.persister(DbPersister)
.bucket('events')
.register();
// Here is an example of a Book model class:
public class Book extends ModelBase {
private String author = "";
private ZonedDateTime publishDate;
private String title = "";
private List categories = list();
private boolean isPublished = false;
private Long publisherId;
private String isbn = "";
private String description = "";
@Column
@AlternativeKey
public String getAuthor() {
return author;
}
public Book setAuthor(String author) {
this.author = author;
return this;
}
@Column
public ZonedDateTime getPublishDate() {
return publishDate;
}
public Book setPublishDate(ZonedDateTime publishDate) {
this.publishDate = publishDate;
return this;
}
@Column
public String getTitle() {
return title;
}
public Book setTitle(String title) {
this.title = title;
return this;
}
@Column
@Converter(cls= JsonListConverter.class)
public List getCategories() {
return categories;
}
public Book setCategories(List categories) {
this.categories = categories;
return this;
}
@Column
public boolean isPublished() {
return isPublished;
}
public Book setPublished(boolean published) {
isPublished = published;
return this;
}
@Column
public Long getPublisherId() {
return publisherId;
}
public Book setPublisherId(Long publisherId) {
this.publisherId = publisherId;
return this;
}
@UniqueKey
@Column
public String getIsbn() {
return isbn;
}
public Book setIsbn(String isbn) {
this.isbn = isbn;
return this;
}
@Column(columnDefinition = "longtext")
public String getDescription() {
return description;
}
public Book setDescription(String description) {
this.description = description;
return this;
}
}
// Here is an example controller class:
public class BooksController extends StandardModelController {
public static BooksController instance() {
return (BooksController)DataAccessRegistry.instance().get("books");
}
public static void register() {
DataAccessRegistry.instance().register(
new DataAccessRegistration()
.setBucket("books")
.setModelClass(Book.class)
.setControllerClass(BooksController.class)
.build();
);
}
// Optional Overrides
public onPreCreatePrepare(Book book) {
if ("".equals(book.getIsbn())) {
book.setIsbn(generateIsbn());
}
}
// Example extensions
public List findByAuthor(String authorName) {
return this.filter("author", authorName).all();
}
};
// Then in your StallionPlugin.boot() override method, register the controller:
BooksController.register();
// You can then access it going forward:
List<Book> books = BooksController.instance().filter("author", "Mark Twain").all();
Creating a new object, saving it, updating it
var book = myContext.dataAccess.books.newModel({title: "A tale of two cities"});
var book.author = "Charles Dickens";
myContext.dataAccess.books.save(book);
var book = myContext.dataAccess.books.forId(bookId);
book.categories.add("drama");
myContext.dataAccess.books.save(book);
// Create a new item
Book book = new Book()
.setTitle("A Tale of Two Cities")
.setAuthor("Charles Dickens")
BooksController.instance().save(book);
// Update a book
Book book = BooksController.instance().forId(bookId);
book.categories.add("drama");
BooksController.instance().save(book);
Allowed model property annotations
// Defines the database column associated with this property
@Column // javax.persistence.Column
@Column(name="first_name", columnDefinition="varchar(50)") // extra options
// Defines a non-unique, indexed field
@AlternativeKey
// defines a converter class that converts the column data to and from the
// database type to the Java property type. For instance, the JsonListConverter
// converts from a database longtext in stringified JSON form to a Java list, and vice-versa.
@Converter(cls=JsonListConverter) // io.stallion.dataAccess.db.Converter
// Makes a unique field
@UniqueKey
// When returning a JSON response via a REST endpoint
// you can restrict the JSON to only generate a
// limited set of the fields
@JsonView(RestrictedViews.Unrestricted.class) // always serialized to JSON
@JsonView(RestrictedViews.Internal.class) // never seralized to JSON from REST endpoints
// Will never be serialized to JSON, even when saving to disk
@JsonIgnore
Finding models matching contraints
var users = myContext.dataAccess.users.find({familyName: Smith});
users = myContext.dataAccess.users.filter("familyName", "Smith").all();
// All users with last name Smith
List<IUser> users = UserController.instance()
.filter("familyName", "Smith")
.all();
// All users with last name Smith, not first name John
users = UserController.instance()
.filter("givenName", "Smith")
.exclude("familyName", "John")
.all();
// The first response of name Adam Smith
IUser user = UserController.instance()
.filter("givenName", "Adam")
.filter("Smith", "John")
.first();
users = UserController.instance()
.filter("createdAt.year", 2015, ">")
.all();
List<FilterGroup<IUser>> groups = UserController.instance().filterChain()
.groupBy("createdAt.month");
© 2024 Stallion Software LLC