Skip to content
This repository has been archived by the owner on Sep 5, 2023. It is now read-only.
Thom van Kalkeren edited this page Oct 6, 2020 · 20 revisions

Overview

These are the regular Ruby on Rails (API) building blocks:

  • Controllers
  • Channels
  • Helpers
  • Mailers
  • Models
  • Views (Or the API equivalent; serializers)

LinkedRails extends upon the Rails toolkit by adding some new blocks and extending others.

LinkedRails extracts the following blocks as first-class citizens:

  • Enhancements; a simple way to add a specific behaviour to a model
  • Actions; define what you can do with a model
  • Forms; define how your users enter information
  • Policies; define rules what should be allowed

LinkedRails adds the following extensions:

  • Collections; like has_many but more extendible and powerful

Enhancements

Enhancements provide an interface to encapsulate all aspects of a specific functionality into a module

Lots of behaviour is shared across models, controllers, and views. For example; Rails allows us to keep track of create/update times, most apps allow users to create and edit models, have some view template for showing an item in a list. There are multiple ways to abstract this behaviour, ruby modules or ActiveSupport Concern for example.

Though none of these options went far enough, which is what enhancements aim to fix. Enhancements allow you to define (nearly) all aspects of a shared behaviour in a concise way.

Because enhancements are meant to encapsulate a certain behaviour, we have a convention to name all enhancements by conjugating the base with the -able suffix, so Update becomes Updatable, Convert becomes Convertible (note the i).

Enhancements are placed in their own folder and have a certain structure:

app/enhancements/<enhancement_name>:

  • action.rb
  • controller.rb
  • model.rb
  • policy.rb
  • routing.rb
  • serializer.rb

Each file contains a module EnhancementName::Segment (E.g. Creatable::Controller) with the code needed for the task.

To enable a specific enhancement for a model, just declare it in the model

class BlogPost < ApplicationModel
  enhance Creatable
end

After which all the modules will be included in the appropriate objects, in the case of Creatable the BlogPostsController will now contain both a new and create method. The only thing we have to do to fill-in the details; define our form and permissions and you can start creating blog posts.

If you need different behaviour for a certain model, you can override an enhancements' behaviour by overriding methods in the appropriate location (e.g. the model or controller).

Existing blocks

A lot of standardization went into existing blocks, some features can be enabled and configured via a DSL, otherbehaviour can be changed by overriding corresponding methods.

Models

DSL

Enhancement DSL keyword Description
Base enhance Enables an enhancement for the model and associated objects
Base with_collection Adds a LinkedRails::Collection on the given association
Tableable with_columns Define multiple column sets for a model (e.g. default)

Overrides

Method name Type Description
::default_collection_display Hash Sets the default way a collection should be rendered

Controllers

Overrides

Method name Type Description
#serializer_params Hash Object given to the serializers (e.g. hash with current_user)
#<action_name>_includes Array List of property names to include with a #action_name response
#preview_includes Array List of property names to include from the members of the collection

New Blocks

Actions

Though not to be confused with controller actions directly, Actions can be seen as an abstraction of these. Actions give interaction capabilities to the system and encapsulate everything that a client needs to fulfil it, this includes the url and HTTP method, a name, a form, a policy, conditions, the result, the status, etc. The base of the actions are schema:Action with some additions like a form.

Since most models have many actions, they are defined in action lists rather than separately. Actions from enhancements are automatically defined, in addition custom actions can be defined by specifying them in the action list.

class BlogPostActionsList < ApplicationActionList
  has_action(
    :publish,
    type: NS::APP[:PublishAction],
    policy: :publish?,
    http_method: :put,
    image: 'fa-send',
    include_object: false,
    url: (obj)-> { publish_blog_post_url(obj) },
    condition: -> { !resource.publication&.publish_time_lapsed? }
  )
end

has_action options

Option Type Description
type IRI The rdf:type of the action.
collection boolean Set to true to define it on the collection (e.g. :new) or false for individual resources (e.g. :edit)
http_method #to_s The http method to fulfil the action with.
policy symbol The method on the policy to call.
image string | IRI The icon of the action
include_paths symbol[] Property path of the object to include (dexes branch)
object resource The object to pre-fill the form with.
url IRI Changes the url of the endpoint to send the request to.
root_relative_iri IRI Changes the url of the action
form LinkedRails::Form The form of the action

Forms

Due to user-experience requirements there is often a difference between what data you store and how entering that data is presented to the user. Often forms don't show all fields or they've been broken into multiple pages. LinkedRails includes a DSL to declare powerful (reusable) forms.

Given the following (simplified) model

class BlogPost < ApplicationModel
  enhance Attachable

  field :name, 
        presence: true,
        length: {maximum: 100}
  field :text, presence: true
end

and the following form

class BlogPostForm < ApplicationForm
  enhance Attachable

  field :name
  field :text, 
        datatype: NS::FHIR[:markdown]
  has_many :attachments

  group :advanced label: 'Options' do
    field :theme,
        datatype: NS::XSD[:string],
        max_count: 1,
        input_field: LinkedRails::Form::Field::SelectInput,
        sh_in: %i[general programming cooking]
  end
end

Note that each model can have a default form (ModelNameForm), but isn't limited to it, which forms you create is up to your UX requirements.

A form has three elements; fields, associations, and groups. In addition, form-specific information can be added like the input type. LinkedRails will automatically try to take extra information from the object passed to the form.

Overrides

Method name Type Description
::model_class Class Set the class to use with this form (needed for custom forms)

has_one/has_many options

Method name Type Description
form Class Set the form to use for this association.

Policies

LinkedRails integrates the the pundit gem to define resource policies.

Conditions are implemented via attribute conditions check_foo

DSL

Method name Type Description
permit_attributes Symbol[], conditions hash Controls attribute permissions
permit_nested_attributes Symbol[], conditions hash Controls nested attribute permissions

Don't forget to add accepts_nested_attributes_for in your model as well.

attribute conditions

The attribute conditions hash can be used to set conditions on whether the given attributes will be permitted. Given options will be called as instance methods on the policy formatted as "check_#{condition_name}" and retrieve the hash value as an argument.

The following conditions are built-in

Method name Type Description
new_record boolean Checks if the record new status equals the given boolean
has_properties { attr_key: boolean } Checks if the record has the given attributes
has_values { attr_key: value } Checks if the record conforms to the given attributes

Overrides

Method name Type Description
#index_children? bool Whether the children can be shown in collections, called on the parent
#<action_name>? bool Controls if the given action is allowed

Enabling

To enable include the following in the Rails base objects

ApplicationModel: include LinkedRails::Model

ApplicationController: include LinkedRails::Controller

ApplicationSerializer: include LinkedRails::Serializer

ApplicationPolicy: include LinkedRails::Policy