Forest is a Rails CMS that makes creating and managing websites easy. It draws inspiration from Wordpress' dashboard and makes it easy to upload images, manage menus, create users with permissions, etc.
rails new myapp --database=postgresql
Add this line to your application's Gemfile:
gem 'forest', git: 'https://github.com/dylanfisher/forest.git'
Install gems
$ bundle
Create database and run migrations
$ rails db:create
Run Forest install generator and follow post-install prompts
$ rails g forest:install
Delete the ApplicationRecord file to inherit from Forest's ApplicationRecord.
Define a root_path
in your host app's routes file
root to: 'pages#show' # or whatever makes sense for your home page controller
Add the blocks directory to Rails' autoload paths:
# application.rb
config.autoload_paths << "#{config.root}/app/models/blocks"
You may want to add the following code to make it easy to monkey patch Forest's classes:
# application.rb
config.to_prepare do
# Load application's model / class decorators
Dir.glob(File.join(File.dirname(__FILE__), "../app/**/*_decorator*.rb")) do |c|
Rails.configuration.cache_classes ? require(c) : load(c)
end
end
Additional instructions and deployment suggestions are outlined in the Wiki.
For an example of a host app running Forest, view github.com/dylanfisher/forest_blog.
Forest aims to include the following features out of the box.
Pages are schedulable and draftable. Each page is composed of a series of blocks that a user can use to create dynamic page layouts. Additional blocks are easy to develop using regular Ruby classes and methods. Forest doesn't use any custom DSL.
An intuitive media browser, similar to Wordpress, is included by default and features multi-file drag and drop upload, and an easy to use modal interface for selecting associated files.
A draggable, nestable interface for managing menus, similar to Wordpress.
Users and user groups, a permissions system, and secure authentication using Devise.
First, run the block type generator. Make sure to restart your server when generating new blocks.
rails generate forest:block TitleAndTextBlock title:string content:text
rails generate forest:scaffold Project title:string media_item:references description:text
Optional arguments:
--skip_all
Equivalent to passing all of the below skip flags
--skip_public
Don't create public controller actions and views
--skip_blockable
Don't add blockable associations
--skip_statusable
Don't add a status column to the model
--skip_sluggable
Don't assume this model needs a slug
A number of custom SimpleForm inputs and components are baked into Forest: https://github.com/dylanfisher/forest/wiki/SimpleForm-inputs-&-components
A serverless on demand video transcoding workflow is built into Forest and relies on a few AWS services. Follow the setup guide for these services at: https://github.com/dylanfisher/forest/wiki/AWS-Serverless-Video-Transcoding.
# in your migration
add_reference :video_blocks, :video, foreign_key: { to_table: :media_items }
# video_block.rb
belongs_to :video, class_name: 'MediaItem'
<%= f.association :media_item, label: 'Video', remote: { path: admin_media_items_path(videos: true) } %>
Add this line to application.rb to tell your host app to use the router to handle errors.
config.exceptions_app = self.routes
Delete the error pages in /public to avoid collision with the custom error pages.
Forest's router will render the appropriate error template via ErrorsController
. Override the placeholder views to customize the error page.
To test the errors in development, set the following config to false.
# development.rb
config.consider_all_requests_local = false
Forest makes it easy to integrate with Elasticsearch. Just include the Searchable
concern in any ActiveRecord models that you want to be searchable.
Then, override the SearchIndexManager method below to indicate which models should be indexed.
SearchIndexManager.class_eval do
def self.indexed_models
[Page, BlogPost]
end
end
Index your documents using the tasks in forest_elasticsearch_tasks.rake. To rebuild the search index, you'd run:
bin/rails forest:elasticsearch:rebuild
A number of configuration options, such as image derivative options, are set in lib/forest.rb
and can be customized in an initializer in your host app:
# Forest.setup do |config|
# config.image_derivative_large_options[:resize][:width] = 3000
# config.image_derivative_large_options[:resize][:height] = 3000
# config.image_derivative_large_options[:jpeg][:quality] = 90
# end
- Delete all files related to the block kind (
_show.html.erb
,edit.html.erb
, etc.). - Create a new migration to destroy existing database entries related to the block:
def up BlockSlot.where(block_type: 'ImageBlock').delete_all BlockKind.where(name: 'ImageBlock').delete_all remove_foreign_key(:image_blocks, :media_items) if foreign_key_exists?(:image_blocks, :media_items) drop_table :image_blocks, if_exists: true end
Forest relies heavily on the following gems, software and frameworks:
- bootstrap
- cocoon
- devise
- has_scope
- jquery
- pagy
- postgres
- pundit
- redcarpet
- shrine
- simpleform
Version 2
- Update settings to support an optional default fallback value for situations where a client may forget to enter a value.
- Update Pundit::NotAuthorizedError on hidden records to return 404, not 500
- Fix common errors in URL, e.g.
app/web.1: [a5d5d477-3e7b-411f-85d6-414af80850f2] ActionView::Template::Error (unexpected ''' after '[:equal, "'/page/some-page/'"]'):
- Remove hidden
block_layout
,block_record_type
,block_kind_id
fields fromblock_slot_fields
and instead apply these values in the controller for better security. - Add pundit policy scoped to admin namespace, e.g.
authorize([:admin, Post])
https://github.com/varvet/pundit#policy-namespacing - Replace authorize index actions with
policy_scope
and make sure policy scope is set up properly - Add ability to toggle each individual blocks hidden status
- Disable choosing a non-image file in the image association
- Add an easier to use API for creating blocks and associating them to records programmatically
- Update shrine file content disposition for non-images so that files have a more accurate file name
- Add Shrine keep_files plugin to prevent deletion of files except in production environment https://shrinerb.com/docs/plugins/keep_files
- move admin helper methods to definition in Admin::ForestController to avoid helper methods on the frontend host app
- Update media library drag and drop to use smaller image thumbnail before image is done processing
- Update scaffold generator to work with namespaced models
- Replace and/or document suckerpunch background job with sidekiq or similar more resilient solution
- Show instances of block kinds in the /admin/block-kinds interface
- Allow configuration of Shrine media upload to allow local file storage
- Better media item input (allow editing caption in place, show point of interest, etc.)
- Don't delete media items from S3 storage in dev environment, better Shrine configuration for accidentally deleting files from S3
- Update site title and description for meta tags
- Add ability to collapse blocks, and (optionally) allow setting default collapse state
- Add a refresh button to media item modal to refresh with newly added images
- Add media item preview icons for PDF/Video and other files. Reference Uppy's pdf icon preview for inspiration
- Media library needs multi-image delete functionality
- Can we add indexes to media item attachment_data jsonb column, e.g. to index if a derivative is present?
- Add ability to redirect to a page association, not just a string field
- Allow menu items to link to reference a page ID in addition to a page path
- scope simpleform configuration in engine so that global config doesn't interfere with host app
- change references to model names and other hard coded names to use I18n
- model resource_description should use I18n
- better pattern for defining custom status message via I18n
- add method to duplicate records
- versioning option for data associated pages, settings, menus, etc. Version diffing ala wordpress.
- better page versioning, page history navigation, restore content blocks with page version
- email confirmations for user related actions
- more robust and flexible permissions system via pundit
- replace glyphicon with bootstrap 4 icons
- tests
- streamlined javascript event triggers namespaced to forest, e.g.
forest:block-slot-after-insert
- potentially refactor how settings are stored, how a setting's value type is inferred, how they are imported, and potentially add a reference to other models so that a model's settings can be displayed when editing a record.
- add description to settings admin index view
- add an easy way to add various metadata and settings to a page without having to add new columns to the
Page
record
Blocks
- refactor block partial to use helper for possible performance gains
- add a duplicate block button to make adding additional blocks of the same type quicker and easier
- add ability to drag certain content types, e.g. media items, between blocks
- minimum and maximum blocks per layout
Media Gallery
- search and replace all legacy references of attachment with new attachment_url method e.g. search for
.attachment.
- add drag and drop upload to media item edit view
- add direct to S3 file upload for large files like video, that otherwise look like they timeout on heroku
- ability to categorize media items on the backend, e.g. different directories of images
- document the new requirements for using the media library, e.g. create the CloudFormation stack, add a background job library to host app's gemfile
If you are interested in contributing to this project please get in touch.
The gem is available as open source under the terms of the MIT License.