BindaCMS/binda

View on GitHub
README.md

Summary

Maintainability
Test Coverage
# Binda
A modular CMS for Ruby on Rails 5.1.

[![Gem Version](https://badge.fury.io/rb/binda.svg)](https://badge.fury.io/rb/binda)
[![Maintainability](https://api.codeclimate.com/v1/badges/8eef73404f871e431560/maintainability)](https://codeclimate.com/github/BindaCMS/binda/maintainability)
[![Test Coverage](https://api.codeclimate.com/v1/badges/8eef73404f871e431560/test_coverage)](https://codeclimate.com/github/BindaCMS/binda/test_coverage)
[![Inline docs](http://inch-ci.org/github/lacolonia/binda.svg?branch=master)](http://inch-ci.org/github/lacolonia/binda)

> This documentation has been written for the [Official Documentation](http://www.rubydoc.info/gems/binda), not the Github README. 
> If you still prefer to read Github README be aware that links might not work properly.

---



# Quick Start

**Binda** is a headless CMS with an intuitive out-of-the-box interface which makes very easy creating application infrastructures.

The core element is the _structure_ element which is the finger print of any _component_ instance. Every _structure_ can have one or more _field-groups_ which can be populated with several _field-settings_. _Field-groups_ and _field-settings_ represent _components_ features, such as galleries, textareas, dates, repeaters and so on.

Let's say your website needs a set of pages with a subtitle. That's super easy. 

- create a "Page" _structure_
- go to General Details of "Page" _structure_ (see the small pencil icon)
- set a "Description" _field-settings_ based both on a _String_ field type. 
- set a "Featured Image" _field-settings_ based both on a _Image_ field type. 

Done! Now you'll see the "Pages" tab in your menu which will contain all your pages and where you can set the description and the featured image.

It's easier learning by doing than by reading. ;-)
Watch the preview on [Binda's vimeo channel](https://vimeo.com/bindacms/010-preview)

![binda-preview-01](https://user-images.githubusercontent.com/1528468/34540454-ea110712-f0d4-11e7-80d5-5464344e1d69.gif)

---



# Installation

Install Binda via terminal

```bash
gem install binda
```

alternatively add the gem to your application's Gemfile:

```ruby
gem 'binda'
```

Then execute:

```bash
bundle install
```

Before completing the installation you need to setup the database. If you are going to use Postgres set it up now.

To complete binda installation run the installer from terminal. Binda will take you through a short configuration process where you will setup the first user and some basic details.

```bash
rails generate binda:install
```

Now you are good to go. Good job!

## Recommended workflow

Binda is totally bound to its database in order to let you structure your CMS directly from the admin panel. The recommended workflow is:

1. **Install** and develop the application locally
2. **Migrate** server and database to production once ready
3. **Restart** after having synched the database. This ensure settings are cached correctly
4. Rinse and repeat 😀

## Reset initial settings

If you need to re-install Binda and reset initial database settings (such as username and password for example) execute the following command from the application root.

```bash
rails generate binda:setup
```

## Credentials

If you have lost your username/password you can run 

```
rails binda:create_superadmin_user
```

This lets you create another temporary super admin user.

## Specific needs
In order to use Carrierwave to process images you need to run MiniMagik. Please refer to [Carrierwave documentation](https://github.com/carrierwaveuploader/carrierwave#using-minimagick) to find more information.

If you need absolute URLs for your images remember to set up your asset host in config/environments/production.rb

```ruby
Rails.application.configure do
  #...
  config.action_controller.asset_host = 'http://your.host.com'
  #...
end
```

If you are not going to use Rails default ORM please check [Carrierwave documentation](https://github.com/carrierwaveuploader/carrierwave#datamapper-mongoid-sequel) and [Devise documentation](https://github.com/plataformatec/devise#other-orms).

---



# Settings

Binda comes with some default preferences.

During the installation process you will be asked to provide the _website name_, _website description_ and the credentials for the default super administrator.

This two preferences can be changed later on inside _Dashboard_ panel visible in the sidebar. 

You can customize the _Dashboard_ panel adding, removing and modifing fields as you prefer. As a matter of fact in _Structures_ you can find the one related to _Dashboard_ which you can edit as you like. The only thing you shouldn't do is to turn it into a component!

You can also create new boards if you need it, you just have to create a structure and set it to `board` via admin panel or assign `instance_type: 'board'` to _structure_ (`your_structure.update_attribute('instance_type', 'board')`).

By default after the installation a `Board` called `dashboard` will be populated with three fields: a `Radio` called `maintenance-mode`, a `String` called `website-name` and `Text` called `webiste-description`.

You can retrieve them this way:

```ruby
B.get_boards('dashboard').first.get_radio_choice('maintenance-mode')
# => return string which can be 'true' or 'false'
B.get_boards('dashboard').first.get_string('website-name')
# => return string with website name
B.get_boards('dashboard').first.get_text('website-description')
# => return text with website description
```

---



# Structures

_Structures_ are the DNA of the application _components_ and _boards_. Each _component_ and _board_ is defined by a _structure_. 

## Create a structure

Creating a _structure_ is fairly easy. Just click on the sidebar tab called _Structures_ and then on the _New structure_ button. You will be asked to provide a name which will be used from then on to call the relative component or board. You can also select the type of structure: _component_ or _board_. The former will let you create multiple instances for that structure whereas the latter will let you create only one instance. Why? A _component_ is great for something like posts, pages and so on. _Board_ are useful for content that is set once throghout the application, for example the website description.

Once the _structure_ has been created it's possible to add field groups. By default there is one field group called *General Details* which is empty. You can customize that or add new ones.

In order to add _field settings_ that will let you add content to your _component_ (or _board_) you need to enter on of the _structure's field groups_.

## Retrieve structure elements

Once you create a _structure_ you can call it from your controller like so:

```ruby
@my_structure = Binda::Structure.find_by(slug: 'my-structure')
```

From that you can do all sorts of things.

```ruby
# get all field groups:
@field_groups = @my_structure.field_groups

# get all field settings:
# (loop method) (returns an Array object)
@field_settings = []
@field_groups.each do |group|
  group.field_settings.each do |setting|
    @field_settings << setting
  end
end
# (single query method) (returns an ActiveRelation object)
@field_settings = Binda::FieldSetting
  .includes(field_group: [:structure])
  .where(binda_field_groups: {structure_id: @my_structure.id})
```

Depending on the structure type, to retrieve all related _components_ or the related _board_ you can use the Binda helper (which is suggested if you care about performance, see [component](#Components) or [board](#Boards)) or do it the usual Ruby on Rails way like so:

```ruby
# if structure is a component type
@components = @my_structure.components

# if structure is a board type
@board = @my_structure.board
```

To retrieve _field groups_ or a _structure_ from a _field setting_ you can do the following:

```Ruby
@field_setting = Binda::FieldSetting.find_by(slug: 'my-field')

# Get the field group
@field_setting.field_group

# Get the structure
@field_setting.structure
```

Note that `@field_setting.structure` is a convenient method Binda offers to get the structure, but don't over think: this doesn't mean there is a association `Binda::Structure  --has_many-->  Binda::FieldSetting`! The association is always mediated by `Binda::FieldGroup`.

---



# Components

_Components_ are instances of a _structure_.

In order to retrieve a single _component_ you can use one of the following methods:

## Using the helper

A useful helper is `B.get_components`. This helper will retrieve all _components_ from a specific _structure_. Find specific info in the [technical documentation](http://www.rubydoc.info/gems/binda/Binda/DefaultHelpers).

Then in any of your controllers you can retrive the components belonging to a specific structure just using the structure slug. Let's see an example that uses the `page` structure to retrieve all related components.

```ruby
B.get_components('page')
# return all pages

B.get_components('page')
     .find_by(slug: 'my-first-page')
# return `my-first-page`

# expand query
B.get_components('page')
     .published
     .order('position')

# reduce N+1 query issue by including dependencies
B.get_components('page')
     .includes(:strings, :texts, repeaters: [:images, :selections])
```

To be able to use this helper in the application console you need to run `Binda.include Bidna::DefaultHelpers`

## Using the rails way

Retrieve a single component

```ruby
@component = Binda::Component.find_by( slug: 'my-first-component')
```

Retrieve a single component but eager load the field setting needed. This optimize the query and greatly reduce request time.

```ruby
@component = Binda::Component.where(slug: 'my-first-component')
                             .includes( :strings, :texts, :assets, :selections )
                             .first
```

Then, if you want to retrieve all components that belongs to a specific structure **don't** do the following:

```ruby
# SLOW
@structure = Binda::Structure.find_by( slug: 'my-structure')
@components = @structure.components
```

**Do this instead!**

```ruby
# FASTER
@components = Binda::Component.where( structure_id: Binda::Structure.where( slug: 'my-structure' ) )

# which is the same thing of doing:
@components = B.get_components('my-structure')
```

You can add any other option to the query then:

```ruby
@components = Binda::Component.where( structure_id: Binda::Structure.where( slug: 'my-structure' ) )
                              .published
                              .order('name')
                              .includes( :strings, :texts, :assets, :selections )

# which is the same thing of doing:
@components = B.get_components('my-structure')
                   .published
                   .order('name')
                   .includes( :strings, :texts, :assets, :selections )
```

## Enable preview

When you created the _component structure_ you might want to enable the **preview mode**. The easiest way to integrate the preview with yor application is to update the `config/routes.rb` file with a redirect that binds the _component_ preview url to the controller that is in chardge of showing that _component_ in your application.

For example let's say you have a *animal* _structure_ with `slug = animal`: 

```ruby 
# your application single animal route
get 'animals/:slug', to: 'animals#show', as: animal

# the bound to a animal preview should be
get "admin_panel/animal/:slug", to: redirect('/animals/%{slug}')
```

---



# Boards

_Boards_ give you the possibility to have a panel where to list some specific settings. 

A great example of a _board_ is the _Dashboard_. This _board_ is useful to store the generic data which can be used throughout the application, like the website name and description.

You can create as many _boards_ you like and add any field you want. To set up a _board_ go to _Structures_ and create a new _structure_ with type "_board_". The name of the _structure_ will determine also the name of the _board_. Once created a new tab will appear on the main sidebar. The newly created _structure_ is already populated with the _General Details_ _field group_ which is initially empty. To add new _field settings_ you can decide to edit this _field group_ or create a new _field group_.

Once ready you can head to the _board_ page by clicking the tab on the main sidebar and populate the _fields_ with your content.

To retrieve _board_ content you can use one of those methods:

```ruby
@board = Binda::Board.find_by(slug: 'my_board')

@board = B.get_boards('my-board').first
```

## Board Helpers

If you care about performance you can use the `Binda.get_board` helper to retrieve the _board_ object.

This method retrieves a **board**. Find specific info in the [technical documentation](http://www.rubydoc.info/gems/binda/Binda/DefaultHelpers).

```ruby
B.get_boards('my-dashboard').first
# return the board

# reduce N+1 query issue by including dependencies
B.get_boards('default-dashboard')
     .includes(:strings, :texts, repeaters: [:images, :selections])
     .first
```

_Boards_ can make use of all field helpers. See the [fields documentation](#Field_Helpers) for more information.

To be able to use this helper in the application console you need to run `Binda.include Bidna::DefaultHelpers`

## Using console

If you are going to use Rails console you need to know that a _board_ is automatically generated once you create a _structure_ with an `instance_type` of `board`.

Example:

```bash
board_structure = Binda::Structure.create!(name: 'new dashboard', instance_type: 'board')
board = board_structure.board
```

---



# Fields

Every _field setting_ is based on a field type. You can create several field settings based on a single field type. 

Here below a list of field types available and their use:

| Field type | Usage | |
|---|---|---|
| string | Store a string. No formatting options available. | [source](http://www.rubydoc.info/gems/binda/Binda/String) |
| text | Store a text. TinyMCE let's you format the text as you like. | [source](http://www.rubydoc.info/gems/binda/Binda/Text) |
| image | Store image. | [source](http://www.rubydoc.info/gems/binda/Binda/Image) |
| svg | Store svg. | [source](http://www.rubydoc.info/gems/binda/Binda/Svg) |
| video | Store video. | [source](http://www.rubydoc.info/gems/binda/Binda/Video) |
| audio | Store audio. | [source](http://www.rubydoc.info/gems/binda/Binda/Audio) |
| date | Store a date. | [source](http://www.rubydoc.info/gems/binda/Binda/Date) |
| radio | Select one choice amongst a custom set. | [source](http://www.rubydoc.info/gems/binda/Binda/Radio) |
| selection | Select one or more choices amongst a custom set. | [source](http://www.rubydoc.info/gems/binda/Binda/Selection) |
| checkbox | Select one or more choices amongst a custom set. | [source](http://www.rubydoc.info/gems/binda/Binda/Checkbox) |
| repeater | Store multiple instances of a field or a collection of fields. | [source](http://www.rubydoc.info/gems/binda/Binda/Repeater) |
| relation | Connect multiple instances of a _component_ or _board_ to each other. | [source](http://www.rubydoc.info/gems/binda/Binda/Relation) |

## Available setting and customization

Sometime you might want to specify a behaviour or a restriction for a specific _field_.
To konw more about a specific _field_ click on the **source** link where you can find a more comprehensive documentation.

## Reload!

In order to keep consistency between _fields_ and their own _settings_ Binda use **callbacks**. 

For example the following line will create a _field setting_, but under the hood it provide each _component_ with the relative _field_:

```ruby
# Create a field setting
@structure.field_groups_first.field_settings.create(name: 'my text', field_type: 'text')
# => <Binda::FieldSetting id: 1, ...>

# You don't have to create a text field for each component, it's alreay been done for you
# WARNING! You won't find immediately the text record associated, you need to reload!
@structure.components.first.texts.first
# => nil
@structure.reload.components.first.texts.first
# => <Binda::Text id: 1, field_setting_id: 1, ...>
```

IMPORTANT: Sometimes callbacks run and the `ActiveRecord` object stored in your variable might get outdated. Use `reload` to make sure the `ActiveRecord` in your variable correspond to the real database record. (run `mycomponent.reload`)

Some callbacks can a bit sneaky. For example _field settings_ with `field_type='radio'` cannot have `allow_null=true` so no matter how many times you try to update `allow_null=true` it will never change.

## How to get field content

Every field setting has a unique `slug`. The default `slug` is made of the `structure name + field group name + field setting name`. If it's a child of a _repeater_ the slug will include the _repeater_ slug as well. You can customise the `slug` as you like keeping in mind that there every `slug` can be attach to only one field setting. 

In order to retrieve a field content you can use one of the following helpers.

Let's say you want to get a specific field from a _component_ instance:

```ruby
# controller file
@article = B.get_components('article').first

# view file
@article.get_text('description')
# => 'Hello world'
```

This helpers will work for any instance of `Binda::Component`, `Binda::Board` and `Binda::Repeater`.

## Field Helpers

Here below a list of helpers. 

You can retrieve field content from a instance of `Binda::Component`, `Binda::Board` or `Binda::Repeater`. See [How to get field content](#How_to_get_field_content).

**NOTE: source links are based on the latest public version.** If you are using an older version or a specific branch please refer to the [source on github](https://github.com/lacolonia/binda/blob/master/app/models/concerns/binda/fieldable_associations.rb) and switch to the branch/tag you are looking for.

| Helper |||
|---|---|---|
| `has_text`| Returns `true/false` | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableTextHelpers:has_text) |
| `get_text`| Returns the text. Use [`simple_format`](https://apidock.com/rails/ActionView/Helpers/TextHelper/simple_format) to maintain HTML tags. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableTextHelpers:get_text) |
| `has_string`| Returns `true/false`. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableStringHelpers:has_string) |
| `get_string`| Returns the text. Use [`simple_format`](https://apidock.com/rails/ActionView/Helpers/TextHelper/simple_format) to maintain HTML tags. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableStringHelpers:get_string) |
|`has_image`| Returns `true/false`.| [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableImageHelpers:has_image) |
|`get_image_url(size)`| Returns the url of the image. A thumbnail version (200x200) by specifying `thumb` size. If no size is provided the method will return the original image size. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableImageHelpers:get_image_url) |
|`get_image_path(size)`| Returns the path of the image. A thumbnail version (200x200) by specifying `thumb` size. If no size is provided the method will return the original image size. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableImageHelpers:get_image_path) |
|`get_image_size`| Returns the image size in MB. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableImageHelpers:get_image_size) |
|`get_image_dimension`| Returns a hash { width: xxx, height: xxx } with image dimension. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableImageHelpers:get_image_dimension) |
|`get_image_mime_type`| Returns the mime type of the image. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableImageHelpers:get_image_mime_type) |
|`has_svg`| Returns `true/false`.| [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableSvgHelpers:has_svg) |
|`get_svg_url`| Returns the url of the svg. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableSvgHelpers:get_svg_url) |
|`get_svg_path`| Returns the path of the svg. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableSvgHelpers:get_svg_path) |
|`get_svg_size`| Returns the svg size in MB. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableSvgHelpers:get_svg_size) |
|`get_svg_mime_type`| Returns the mime type of the svg. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableSvgHelpers:get_svg_mime_type) |
|`has_video`| Returns `true/false`.| [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableAudioHelpers:has_video)|
|`get_video_url`| Returns the url of the video. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableAudioHelpers:get_video_url) |
|`get_video_path`| Returns the path of the video. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableAudioHelpers:get_video_path) |
|`has_audio`| Returns `true/false`.| [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableAudioHelpers:has_audio) |
|`get_audio_url`| Returns the url of the audio. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableAudioHelpers:get_audio_url) |
|`get_audio_path`| Returns the path of the audio. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableAudioHelpers:get_audio_path) |
|`has_date`| Returns `true/false` | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableDateHelpers:has_date) |
|`get_date`| Returns the date in `datetime` format. Use [`strftime`](https://apidock.com/rails/ActiveSupport/TimeWithZone/strftime) to change date format. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableDateHelpers:get_date) |
|`has_repeaters`| Returns `true/false`| [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableRepeaterHelpers:has_repeaters) |
|`get_repeaters`| Returns an array of repeaters. See next session for more details. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableRepeaterHelpers:get_repeaters) |
|`get_selection_choices`| Returns an hash with label and value of the selected choice. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableSelectionHelpers:get_selection_choices) |
|`get_radio_choice`| Returns an hash with label and value of the selected choice. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableSelectionHelpers:get_radio_choice) |
|`get_checkbox_choices`| Returns an array of label/value pairs of all the selected choices. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableSelectionHelpers:get_checkbox_choices) |
|`has_related_components`| Check if has related components. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableRelationHelpers:has_related_components) |
|`get_related_components`| Retrieve related components. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableRelationHelpers:has_related_components) |
|`has_related_boards`| Check if has related boards. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableRelationHelpers:has_related_boards) |
|`get_related_boards`| Retrieve related boards. | [source](http://www.rubydoc.info/gems/binda/Binda/FieldableAssociationHelpers/FieldableRelationHelpers:has_related_boards) |

If you need to get each dependent of all relations with a specified slug (or slugs) you can use `B.get_relation_dependents` helper. This is very useful to retrieve only the instances which have a owner (and therefore are 'dependents'). 

For example, you have several `event` components, each related to several `artist` components with a `partecipants` relation field where every event owns some artists. If you want to retrieve all artists which have been involved in at least one event you can try with 

```ruby
B.get_relation_dependents('partecipants')
# returns all artists which are related to at least one event
```

If you want to retrieve each owner of all relations with the specified slug (or slugs) you can do the following:

```ruby
B.get_relation_owners('partecipants')
# returns all events which are related to at least one artist
```


---



# Repeaters

Generally a _field setting_ is associated to a single content entry. Therefore if a _field setting_ has type `text` there will be only one `Binda::Text` related to it.

If you want to have multiple entries for a single _field setting_ you need to create a _repeater_. 

For example: lets say you have a _Movie_ component and you need to list some credits. You can create a _repeater_ and add a field setting with type `string` and name it _credit_. In the _movie_ editor you will able to add as many credit field you like. 

Another example: imagine you setup a repeater with two children, a string and a asset field.

```
page (structure)
|__ default details (field_group)
    |__ slide (repeater)
        |__ title (string)
        |__ image (asset)
```

Then on the component editor you can

```
My first page (component)
|__ slide_1
    |__ 'My first slide'
    |__ img_1.png
|__ slide_2
    |__ 'My second slide'
    |__ img_2.png
|__ slide_3
    |__ 'My last slide'
    |__ img_999.png

```

The code can be something like this:

```ruby
@page = B.get_components('page')
             .where(slug: 'my-first-page')
             .includes(repeaters: [:texts, :images])
             .first

@page.get_repeater('slide').each do |slide|
  slide.title
  slide.get_image_path
end
```

To be able to use this helper in the application console you need to run `Binda.include Bidna::DefaultHelpers`

The repeater model `Binda::Repeater` can make use of any of the [field helpers](#Field_Helpers).

---



# Users

Binda offers two main roles. The **super admin** which is capable of administrating the entire website and the **standard admin** user which cannot manage the structures, field groups and field settings.

In case you cannot access with your account anymore you can create a new **super admin** via console running this task:

```bash
rails binda:create_super_admin
```

---


# Maintenance Mode

Binda offers a maintenance mode out-of-the-box. In your routes you will find:

```ruby
# config/routes.rb
get 'maintenance', to: 'maintenance#index', as: 'maintenance'
```

You can change the url to be whatever you like, as long as you keep the route name. For example

```ruby
# config/routes.rb
get 'under_construction', to: 'maintenance#index', as: 'maintenance'
```

The maintenance behaviour is controlled by the `MaintenanceHelper` included in your `app/controllers/application_controller.rb`. If you don't have it make sure it's included this way: 

```ruby
# app/controllers/application_controller.rb
include ::Binda::MaintenanceHelpers
```

## Customize maintenance appeareance

The maintenance mode is controlled by the `app/controllers/maintenance_controller.rb` which renders a single view: `app/views/layouts/maintenance.html.erb`. You can do whatever you like with it.

To change appereance and behaviour of the page add your styles to `app/assets/stylesheets/maintenance.scss` and your scripts to `app/assets/javascript/maintenance.js`. These are manifest files so if you need jQuery for example, you just need to add `//= jquery` at the top of `maintenance.js` file.

---

# Field settings and field groups
    
## Orphans

Sometime playing with Rails console you might end up creating orphans, which basically are children of a parent that doesn't exist anymore. They might cause errors in some queries and they are hard to track down.

To make sure you haven't any orphan run the following commands from your shell:
 
```bash
rails binda:remove_orphan_fields
rails binda:remove_orphan_components
rails binda:remote_orphan_boards
```

## Missing Field instances

It might happen that a board or component doesn't have a field even though a field setting exists. This might be caused by an improper use of the rails console. If you're paranoid about it run the following command:

```bash
rails binda:create_missing_field_instances
```
---


# Plugins

Here a list of useful plugins:

- [Binda API](https://github.com/lacolonia/binda-api)
- [Binda Shopify](https://github.com/lacolonia/binda-shopify)

---



# Upgrade from a previous version

If you are going to upgrade from a previous version of Binda please check the guidelines attached to the version release which can be found on this [Github page](https://github.com/lacolonia/binda/releases).

---



# Create a Binda plugin

You can create a plugin to add new features to Binda. This is the most suitable and correct way to develop a new feature that will be possibly shared and use by everyone in the future.

The first step is to create a plugin.
```
rails plugin new binda_new_feature --skip-test --dummy-path=spec/dummy --mountable
```
This will create a folder `binda_new_feature` which will contain your plugin `BindaNewFeature`. 

We will `--skip-test` as we are going to use Rspec instead of standard Ruby on Rails test suite. For the same reason the `--dummy-path` will change to `spec/dummy`. Ultimately we want the plugin to be `--mountable`.

## Add dependencies
Populate `binda_new_feature.gemspec` with dependencies and replace every `TODO` with a content that makes sense.

```ruby
# binda_new_feature.gemspec

$:.push File.expand_path("../lib", __FILE__)

# Maintain your gem's version:
require "binda_new_feature/version"

# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
  s.name        = "binda_new_feature"
  s.version     = BindaNewFeature::VERSION
  s.authors     = ["Me"]
  s.email       = ["me@mydomain.com"]
  s.homepage    = "http://mydomain.com"
  s.summary     = "Binda New Feature is plugin for Binda CMS"
  s.description = "Use this plugin to enable new feature in your application"
  s.license     = "MIT"

  s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]

  s.add_dependency "rails", ">= 5.0", "< 5.2"
  s.add_dependency "binda", "~> 0.0"

  # Test suite
  s.add_development_dependency "rspec-rails", ">= 3.5", "< 3.7"
end
```

Make sure all dependencies are versioned in order to avoid issues due to deprecation in future releases. (The above versions are dated 07/2017)

## Prepare plugin for testing
Before coding anything make sure you complete the following steps: 


1) Install Rspec

```
rails generate rspec:install
```

2) Install Binda on the dummy application

```
cd spec/dummy
rails generate binda:install
```

3) Tell `config.generators` to use Rspec:

```ruby
# lib/binda_new_feature/engine.rb

module BindaNewFeature
  class Engine < ::Rails::Engine
    isolate_namespace BindaNewFeature

    config.generators do |g|
      g.test_framework :rspec
    end

  end
end
```

This makes sure Rails uses Rspec to create test specs every time you generate something with `rails g` command (be it a model, controller or scaffold).

4) change a line in `spec/rails_helper.rb`

```ruby
# require File.expand_path('../../config/environment', __FILE__)
# should be changed to
require File.expand_path('../dummy/config/environment', __FILE__)
```

## Create a first test
Let's create a fake controller to see if the plugin works.

```
rails generate controller binda/foo index --skip-namespace
```

This command create a controller called `Binda::Foo` which will be integrated to `Binda` engine and, we have done everything correctly, we should have a spec ready to be populated with tests in `spec/controllers/binda/foo_controller_spec.rb`.

Lets add Binda routes to that spec. (See why you need to specify routes [here](https://content.pivotal.io/blog/writing-rails-engine-rspec-controller-tests))

```ruby
# spec/controllers/binda/foo_controller_spec.rb

require 'rails_helper'

module BindaNewFeature
  RSpec.describe FooController, type: :controller do

    # This line is very important
    routes { Binda::Engine.routes }

    describe 'GET #index' do
      it 'returns http success' do
        get :index
        expect(response).to have_http_status(:success)
      end
    end

  end
end
```

The generator unfortunately creates a namespace inside `BindaNewFeature::Engine.routes` which we will not use. Instead add the folloing lines which uses `Binda::Engine.routes`

```ruby
# config/routes.rb

BindaNewFeature::Engine.routes.draw do
end

Binda::Engine.routes.draw do
  get 'foo', to: 'foo#index'
end
```

Now running `rspec` the test should pass. (you might have 2 pending examples for foo helper and view, but that's not a problem for now).

## Extend a Binda models and controllers
Sometimes you want to add new methods to Binda models with your plugin. In order to do it you need to make your plugin aware of Binda and its models. To achive it require Binda at the top of the `lib/binda_new_feature/engine.rb` like so:

```ruby
require "binda"
```

If you want to access Binda::ApplicationController to inherit its methods change the parent_controller configuration of your plugin in the same file:

```ruby
# lib/binda_new_feature/engine.rb

# ... all `require` gems

module BindaNewFeature
  class Engine < ::Rails::Engine

    # ... some other code

    config.parent_controller = 'Binda::ApplicationController'
  end
end
```

---



# How to create a form of nested components

Let's say you have a component which depends on another components and you want your user to edit both of them in the same form. No problem.

```ruby
# controller
# Let's say your structure has slug = `my_structure` and id = `123`
# and you want to edit `component_A` and its child `component_B` 

@structure = Binda::Structure.find(123)
# or if you use Friendly_id
@structure = Binda::Structure.friendly.find('my_structure')

@component_A = @structure.components.detect{|c| c.slug == 'component_A'}
@component_B = @structure.components.detect{|c| c.slug == 'component_B'}
```

```erb
# view
<%= simple_form_for @structure, html: { class: 'some-form-class' } do |f| %>
  <%= f.simple_fields_for :components, @component_A do |A| %>
    <=% A.input :name_of_a_column %>
    <=% A.input :name_of_another_column %>
  <% end %>
  <%= f.simple_fields_for :components, @component_B do |B| %>
    <=% B.input :name_of_a_column %>
    <=% B.input :name_of_another_column %>
  <% end %>
<% end %>
```

---



# How to contribute

Any contribution is more than welcome.

To contribute [fork this project](https://github.com/lacolonia/binda/wiki/_new#fork-destination-box) and clone the fork in your local machine. There you are free to experiment following this principles:
- before diving into the code [open a issue](https://github.com/lacolonia/binda/issues/new) to explain what you'd like to do
- don't add gem dependencies unless it's absolutely necessary
- keep it simple and be DRY
- add [tests](#How_to_test)
- comment your code following [Yard guidelines](http://www.rubydoc.info/gems/yard/file/docs/GettingStarted.md) principles (use `yard server -r` to see the HTML version at `http://localhost:8808`, if you make any change to the doc just refresh the page and the documentaion page gets updated automagically)
- update the README.rb file, use Github markdown
- if you are not adding a core feature consider writing a plugin instead
- improve and/or add new I18n translations
- when fixing a bug, provide a failing test case that your patch solves
- write deprecation warning for methods instead of deleting them (`app/models/concerns/binda/deprecations.rb`)

## How to work locally
Ensure you have installed Binda dependencies.

```bash
cd path/to/binda
bundle install
npm install
```

To see what you are actually doing you can make use of the **dummy application** which is shipped with Binda.

Ensure you have Postgres up and running, then create dummy databases.

```bash
cd spec/dummy
rails db:create
```

If you haven't already, install Binda.

```bash
rails generate binda:install
```

This should automatically launch binda:setup, which prompts you to enter an email address and a password.
If this doesn't enter you have to launch it manually

```bash
rails generate binda:setup
```

In order to edit javascript files you need to run Webpack and leave the terminal window open, so Webpack can compile everytime you save a file. To install Webpack run `npm install` from the root of your application. Then everytime you want to edit a javascript file run:

```bash
webpack
```

If you need to reset your database run the following commands

```
cd spec/dummy
rails db:drop && rails db:create
rails generate binda:setup
```

In order to make the dummy application flexible any update to that folder isn't saved in the repository.

This let you as you prefer with your dummy without the hassle of cleaning it before creating a commit.

## How to test
In order to avoid the *it works on my machine* issue, test are run via Travis every time a commit is pushed. Make sure you register your forked version on Travis in order to test every commit. If you don't the forked version will be tested once you make a pull request to the original repository.

If you want (and you should) test locally Binda use RSpec, FactoryGirl and Capybara to run tests. You can find all specs in `spec` folder. Capybara needs Firefox and [Geckodriver](https://github.com/mozilla/geckodriver) to run so make sure you have it installed in your machine. If you have Node you can install Geckodriver via npm:

```bash
npm install --global geckodriver
```

Some specs are run against the database. If you haven't installed Binda on the dummy application yet run:

```bash
RAILS_ENV=test rails db:migrate
```

Sometimes this isn't possible because you have already a database present and other times you might get `ActiveRecord::PendingMigrationError: Migrations are pending. To resolve this issue, run: bin/rails db:migrate RAILS_ENV=test` Therefore, instead of `db:migrate` use the following command:

```bash
RAILS_ENV=test rails db:reset
```

The above command might generate an error. This is probably because you have previously installed Binda and the generator finds migration both in `binda/db/migrate` and `binda/spec/dummy/db/migrate`. To solve the issue, remove the `spec/dummy/db/migrate` folder and run the previous command again.

```bash
cd spec/dummy
rm -rf db/migrate
rails db:drop
rails db:create
rails db:test:prepare
cd ../.. 
```

Here the oneliner: 

```bash
cd spec/dummy && rm -rf db/migrate && rails db:drop && rails db:create && rails db:test:prepare && cd ../.. 
```

If Binda migration have been updated then your `schema.rb` is outdated and will generate false failing tests. In this case you need to run following command to refresh your database configuration:

```bash
cd spec/dummy
rm -r db/schema.rb
rails db:drop
rails db:create
rails generate binda:install
rails db:test:prepare
rm -rf db/migrate
rm -rf config/initializers/devise_backup_*.rb
cd ../..
```

Here the oneliner:

```bash
cd spec/dummy && rm -r db/schema.rb && rails db:drop && rails db:create && rails generate binda:install && rails db:test:prepare && rm -rf db/migrate && rm -rf config/initializers/devise_backup_*.rb && cd ../..
```

If in the future you need to clean your dummy app code, simply run:

```bash
rm -rf spec/dummy && git checkout spec/dummy
```

**The command above should be run before any commit!**

Once all setup is done run RSpec every time you update the specs:

```bash
rpsec
```

Some helpful hints to debug tests are:

1. Add `save_and_open_page` command in the code of the test example. This will save the page and let you inspect it.
2. Add `binding.pry` this will stop the test and let you inspect the code at that moment of the code.
3. In the command line, from Binda root folder, execute `tail -f ./spec/dummy/log/test.log` this will give you the list of operations executed by the server while you are running the test.

## Update test coverage

Once tests are done update Code-Climate test coverage locally (Mac OSX).

```bash
$ cd path/to/binda
$ curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-0.1.4-darwin-amd64 > ./cc-test-reporter
$ chmod +x ./cc-test-reporter
$ ./cc-test-reporter before-build
$ rspec
$ ./cc-test-reporter after-build -r 7bffb1da50f35979ea3ef4fc205aa03c6b3c10fa603d5b66f19f81ab06d2ab97
```

`cc-test-reporter` is ignored by the repo, so it want be pushed.

Same thing can be done on linux usign another binary code (see [documentation](https://docs.codeclimate.com/docs/configuring-test-coverage)). Besides the test coverage can be done automatically via Travis as well, but not on pull requests.

---



# Binda versioning

It's possible to test edge versions of Binda on real projects. Edge versions can be found only in the github repository and can be referenced by tag.

For example once `v0.1.0` is published any new edge release which can be considered stable enough for a real project is tagged with alpha or beta (`v0.1.1.alpha`, `v0.1.1.alpha.1`, `v0.1.1.beta`, etc). These tags won't change and won't be removed so you can safely add them to you gemfile like so:

```ruby
gem 'binda', github: 'lacolonia/binda', ref: 'v0.1.1.alpha.1'
```

The same tag is listed as the gem version, but it's not published to Rubygems.

More info can be found at the [semantic versioning documentation](https://semver.org/spec/v2.0.0-rc.1.html).
---



### Bug reporting
Please refer to this [guide](http://yourbugreportneedsmore.info).
If you need direct help you can join [Binda Slack Channel](https://bindacms.slack.com).


### License
The gem is available as open source under the terms of the [GNU General Public License v3.0](https://github.com/a-barbieri/binda/blob/master/LICENSE).


Binda is a headless CMS with an intuitive out-of-the-box interface.

Copyright (C) 2017  Alessandro Barbieri

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 of the License.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

Find the GNU General Public License here <http://www.gnu.org/licenses/>.

### Open Source Tools

Binda is supported by those amazing services which are giving invaluable help in making Binda a better tool:

|||
|---|---|
|<img src="https://www.dropbox.com/s/0n9042d6c5jjiw5/jetbrains.png?raw=1" width="90"> | [Jet Brains](https://www.jetbrains.com) is the main tool for developing Binda code.|
|<img src="https://www.dropbox.com/s/cj4vehhl2dpfzhw/browserstack-logo-600x315.png?raw=1" width="200">|[Browserstack](https://www.browserstack.com/) is the best way to make sure Binda interface is consistent on every browser and device.|
|<img src="https://www.dropbox.com/s/fxd1vich31sbhp6/code-climate-logo-png-transparent.png?raw=1" width="180">|[Code Climate](http://codeclimate.com/) is an amazing tool to ensure code quality are at the highest standard.| 
|<img src="https://www.dropbox.com/s/89xzo1tutve9lva/TravisCI-Full-Color.png?raw=1" width="160">|[Travis CI](http://travis-ci.org) takes care of testing every pull request which is an invaluable help.|

### Credits
Binda is inspired by [Spina CMS](https://github.com/SpinaCMS/Spina).

We give also credit to authors and contributors of the gems that Binda uses. Huge thank you to all of them.

### Who is Binda?
Is [this guy here](https://en.wikipedia.org/wiki/Alfredo_Binda).

![Alfredo Binda 1927](https://www.dropbox.com/s/ktv8qo13zvoc9g4/Alfredo_Binda_1927.jpg?raw=1)