-
Notifications
You must be signed in to change notification settings - Fork 1
Home
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 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).
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.
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 ) |
Method name | Type | Description |
---|---|---|
::default_collection_display | Hash | Sets the default way a collection should be rendered |
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 |
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
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 |
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.
Method name | Type | Description |
---|---|---|
::model_class | Class | Set the class to use with this form (needed for custom forms) |
Method name | Type | Description |
---|---|---|
form |
Class | Set the form to use for this association. |
LinkedRails integrates the the pundit gem to define resource policies.
Conditions are implemented via attribute conditions check_foo
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.
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 |
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 |
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