Ignore

Please note that your browser is not supported.

We recommend upgrading to the latest Firefox or Google Chrome.

Reflection: Data Sources

Data sources help model dynamic data stored in a database, defining aspects such as attributes, types, and associations. They also define actions for creating new data and queries that return results from the database. Data sources make up part of the backend of your project. The frontend doesn't interact directly with data sources, instead relying on backend actions and endpoints defined in controllers to move data to and from the frontend.

In this guide we'll focus on how Pakyow creates reflected data sources based on your frontend view templates. The next few guides that follow will walk you through adding layers of behavior on top of the data sources through actions and endpoints.

Ready? Let's get started!

When Pakyow finds a binding type in your view template, it creates a matching data source with all the attributes of the binding. For example, here's a view template that presents a message:

<article binding="message">
  <h1 binding="subject">
    ...
  </h1>

  <p binding="content">
    ...
  </p>
</article>

In this case, Pakyow defines a messages data source with two attributes: subject and content. The name of the data source is simply a plural version of the binding type. Running the info:sources command prints out details about the reflected source:

:messages pakyow/reflection
  attribute :id,         :bignum
  attribute :content,    :string
  attribute :subject,    :string
  attribute :created_at, :datetime
  attribute :updated_at, :datetime

Auto migrate

When you boot your project, Pakyow automatically creates a matching messages table in the database. The table is named after the messages data source and contains a column for every attribute defined on the source. This is the result of Pakyow's auto migrate feature.

Auto migrate works behind the scenes to keep your database structure in sync with your data sources. This is handy when you're developing because you don't have to stop and think too much about your database structure.

Once you finish a feature any changes to the database structure can be captured as physical database migrations that live in your project's codebase. Each migration consists of Ruby code that describes what changes need to be made to the database, whether it's creating a new table, adding a column, or changing a type.

Database migration files are created for you with the pakyow db:finalize command. One finalized migration file will be created for every change needed to put the database in the correct state for your project.

Attribute types

Each attribute on a data source has a type. Let's inspect the source defined above:

:messages pakyow/reflection
  attribute :id,         :bignum
  attribute :content,    :string
  attribute :subject,    :string
  attribute :created_at, :datetime
  attribute :updated_at, :datetime

Both attributes are string types. Pakyow assumes all binding attributes to be strings by default, but there are a few cases where a more specific type is chosen.

Inferred types from names

In some cases the attribute type can be pulled from the name of the attribute itself. One such case is with date attributes. A convention in Pakyow is to name datetime fields like this:

{attribute}_at

Data sources use this convention to define created_at and updated_at attributes that track when a record was created and updated. When Pakyow encounters a binding attribute with an _at suffix, it defines the attribute as a datetime type in the data source.

Inferred types from form fields

Attribute types can also be inferred from form fields. There are currently two types that will be defined this way: datetime and decimal. Attributes are assigned the datetime type when defined on any of these form fields:

<input type="datetime">
<input type="datetime-local">
<input type="time">

Attributes are assigned the decimal type when defined on any of these form fields:

<input type="number">
<input type="range">

Specifying other types

Not all data types can be inferred from the attribute name or defined on a form field. The best way to handle these cases is to extend the reflected data source and definethe exact types that you want. We'll talk about extending data sources later in this guide.

Associations

Assocations describe how the data sources in your application are related. Pakyow discovers associations between data sources based on a few aspects of the frontend view template structure. All of the reflected association behavior is included below.

Nested binding types

When Reflection encounters a binding type nested within another type, it assumes that the two types are related to each other. For example:

<article binding="message">
  <article binding="reply">
    <p binding="body">
      reply body goes here
    </p>
  </article>
</article>

Because reply is nested in message, the reflected messages data source will contain a has_many :replies association. The reciprocal association is also defined for replies back to messages, meaning the replies data source will contain a belongs_to :message association.

Pakyow manages associations for you, but a basic understanding of how associations are represented in the database can be helpful. In practical terms, associations define columns in the database that tie data together. In the has_many/belongs_to case that we have here, the replies table will contain a foreign key named message_id that contains the id of the related message.

Nested view paths

Pakyow defines assocations for sources that appear in nested view paths. For example:

frontend/pages/posts/comments/new.html
<form binding="comment">
  ...
</form>

The form isn't defined in the post binding type but will still belong to the posts source. This is because the comment binding type is located at a view path within the RESTful resource path for posts (/posts).

Custom associations

You may need to change the default association behavior from time to time. A common case is needing a has_one association between sources instead of the default has_many association. Here's an example:

<article binding="message">
  <div binding="user">
    <span binding="name">
      user name goes here
    </span>
  </div>
</article>

Pakyow assumes that messages have many users. But in this case the view template is attempting to present the author for a message. Since a message can only have one author, a has_one association would be more appropriate. This can be done by creating a backend data source and defining the association:

source :messages do
  has_one :user
end

Reflection will still extend the data source with other associations, but will use the defined association for users.

Connection types for data sources

When Pakyow creates a reflected data source, it assigns one of two connection types: default or memory. The default connection is used for data that is long-lived and should continue to exist even if the application restarts. The memory connection is used for less important data that can come and go as needed. You can think of the memory connection as a cache that can be queried with SQL.

Pakyow assigns a connection type to a source based on its perceived intent. If a form is defined for a binding type, Pakyow assumes that the data should be persistent. Data sources with a related form are assigned the default connection, usually pointing to a local SQLite file or a Postgres database.

On the other hand, binding types that don't have a form are assigned the memory connection. Pakyow assumes that these types will be used for temporary data, or data that is loaded every time the project boots. Pakyow automatically creates an in-memory SQLite database for memory sources.

These decisions make good default conventions, but your project may not follow these rules at all times. If you need to make an exception, you can manually assign a connection type to any data source. We'll cover how in the next section on customization.

Customizing reflected data sources

Pakyow makes it possible to customize reflected data sources to handle cases that fall outside of what reflection is designed for. To customize a data source, create a data source in the backend/sources folder:

source :messages do
  attribute :view_count, :integer
end

Reflection will extend the data source with attributes and associations it discovers, but won't override any behavior you define yourself. To set a custom type for a reflected attribute, just define the attribute with the type that you want:

source :messages do
  attribute :some_reflected_attribute, :boolean
end

Learning more

Reflected data sources build on top of the backend data source primitive found in the pakyow/data framework. We covered some aspects of data sources briefly in this guide, but we've really just scratched the surface. To learn more, use the links below:

Next Up: Endpoints