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

  1. 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.
  2. 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.
  3. 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.
  4. 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