Outpost

Outpost Sign

Circle CI

A Rails Engine for quickly standing up a CMS for a Newsroom.

Dependencies

  • rails >= 3.2
  • ruby >= 1.9.3

See .travis.yml to see which Ruby versions are officially supported.

Installation

Add gem 'outpost-cms' to your Gemfile. The module you interact with is just Outpost.

A note about the gem/repository/module name discrepancy
There is another gem called "Outpost" which occupies the same namespace as this gem. However, the other Outpost is meant for service monitoring, and I can't imagine a scenario where these two gems would be used together in the same application. Therefore, I'm keeping the module name, and just renaming the gem to outpost-cms so we can both exist on RubyGems.

This gem also has some hard dependencies that aren't in the gemspec. My goal is to reduce these dependencies as much as possible, but as this was extracted from the KPCC application, these are fairly strict at this point.

  • simple_form - for Rails 3.2, use ~> 2.1.0. For Rails 4.0, you'll need to use ~> 3.0.0.beta1
  • kaminari - You need to use the kaminari master branch.
  • eco
  • sass-rails
  • bootstrap-sass
  • coffee-rails

Usage

Configuration

Outpost has some required configuration. In an initializer, perhaps outpost.rb, register your "outpost models" (first-class models which are managed directly through Outpost), as strings. Other available configuration will be discussed throughout this documentation.

Outpost::Config.configure do |config|
  config.registered_models = [
    "Article",
    "Blog",
    "User"
  ]

  # Attributes which should be looked for as "title attributes", used for representing
  # the object throughout Outpost.
  config.title_attributes      = [:name, :headline, :short_headline, :title]

  # For controllers without a list defined, Outpost will render a list
  # with all of the attributes. Add attributes here which should always be
  # excluded from these automatic lists.
  config.excluded_list_columns = ["body"]
end

Authentication

Much like Devise, Outpost provides a basic SessionsController and corresponding views. Their routes are part of the Outpost::Engine routes.

Outpost also provides the Outpost::Model::Authentication module, which you should include into your User model to work with the provided SessionsController:

class User < ActiveRecord::Base
  include Outpost::Model::Authentication
end

Your User class should have at least the following methods: * password_digest (string) * last_login (datetime) * can_login (boolean) * is_superuser (boolean) * name (string)

Configuration

You can set a different User class, or the attribute which the user should use to login:

Outpost::Config.configure do |config|
  config.user_class                   = "AdminUser"
  config.authentication_attribute     = :username
end
Routes

Mount Outpost's routes outside of your outpost namespace. Your custom-defined namespace will be mostly for defining resources. You can also add a catch-all route (see example) at the very bottom of the namespace to use outpost-specific 404 pages. If you don't add this line, then 404 pages in the /outpost namespace will render your app's normal 404 page.

Rails.application.routes.draw do
  # ...

  mount Outpost::Engine, at: "outpost"

  namespace :outpost do
    resources :posts

    # Add outpost-specific 404 pages.
    # This must be at the very bottom of the namespace.
    get "*path" => 'errors#not_found'
  end
end

This mount provides a few routes: * outpost.root_path - The path to the dashboard. * outpost.login_path - Login * outpost.logout_path - Logout

Authorization

Outpost comes with a built-in Permission model, whose only attribute is a String resource, which stores a class name which you want to be authorized throughout the application. Run this migration to set it up:

create_table :permissions do |t|
  t.string :resource
  t.timestamps
end

create_table :user_permissions do |t|
  t.integer :user_id
  t.integer :permission_id
  t.timestamps
end

add_index :permissions, :resource
add_index :user_permissions, :user_id
add_index :user_permissions, :permission_id

You can include Outpost::Model::Authorization into your User model to provide the Permission association, and also add the can_manage? method:

if !current_user.can_manage?(Post)
  redirect_to outpost.root_path, alert: "Not Authorized"
end

Authorization is "All-or-None"... in other words, a user can either manage a resource or not - A user with permission for a particular model is able to Create, Read, Update, and Delete any of those objects.

Outpost controllers will automatically authorize their resource. Within views, you can use one of the provided helpers to guard a block of text or a link:

<%= guard Post do %>
  Only users who are authorized for Posts will see this.
<% end %>

<%= guarded_link_to Post, "Linked if authorized, plaintext if not", posts_path %>

User Preferences

Preferences are stored in the session, and on a per-resource basis. Outpost provides built-in hooks in the controller and views for Order (attribute) and Sort Mode ("asc", "desc"). In order to manage other preferences, you'll want to make use of a handful of methods that get mixed-in to your Outpost controllers:

  • preference - Access a preference's value.
  • set_preference - Set a preference's value.
  • unset_preference - Unset a preference's value.

The key for a preference needs to follow the convention:

"#{model.content_key}_#{preference}"

For example:

set_preference("blog_entries_color", "ff0000")

You also need to add the parameter that the preference is using to config.preferences:

Outpost::Config.configure do |config|
  # ...
  config.preferences += [:color]
end

A resource-based preference is automatically cleared if its param is an empty string (not nil). For example:

# GET /outpost/posts?color=ff0000
set_preference('posts_color', params[:color])
preference('posts_color') # => ff0000

# GET /outpost/posts?color=
preference('posts_color') # => nil

If you have a preference for a non-resourceful page, you need to manage its cleanup manually.

Javascripts

Outpost comes with a bunch of useful scripts built-in. Some of them are automatically used, and some are provided as "opt-in" functionality.

Field Counter

Field Counter

This will add a counter above any field which will show the number of characters entered into that field, the target length, and the +/- fuzziness, as well as a color indicating where in that range they are.

Use

Add the class field-counter to a div wrapping the input field, and two data-attributes containing integers:

  • data-target - The target length (default: 145)
  • data-fuzziness - The fuzziness allowed (default: 20)

If you're using simple_form, it might look like this:

f.input :title, wrapper_html: { class: "field-counter", data: { target: 50, fuzziness: 10} }

Preview

Preview

The Javascript for Preview is what handles sending the form data to the server, but you'll need to handle the server-side stuff yourself. The "Preview" button will show up once you've added a preview action to that controller.

Use

The preview action needs to do a few things:

  • Find the object from the passed-in obj_key (You can use Outpost::obj_by_key). You'll also need to handle what happens if the record hasn't been saved yet.
  • Merge in the changed attributes.
  • Render the proper template/layout, or any validation errors (using render_preview_validation_errors).
  • Make sure you don't save anything. For this, I recommend doing any object updating inside of a database transaction, because assigning associations to a persisted object will save the object. Outpost provides a controller method, with_rollback, which will perform the block inside of a database transaction and force an ActiveRecord::Rollback at the end.

Here is a full example of what your preview action could look like:

def preview
  @post = ContentBase.obj_by_key(params[:obj_key]) || Post.new

  with_rollback @post do
    @post.assign_attributes(form_params)

    if @post.valid?
      render "/posts/_post", layout: "application", locals: { post: @post }
    else
      render_preview_validation_errors(@post)
    end
  end
end

You'll also need to add two routes for the preview action:

resources :posts do
  put "preview", on: :member
  post "preview", on: :collection
end

You need both post and put to allow the preview to happen from either the New or Edit pages. If you're using Rails 4, use patch instead of put. In fact, if you're using Rails 4 (or the routing_concerns gem), then you can use Routing Concerns:

concern :previewable do
  patch "preview", on: :member
  post "preview", on: :collection
end

resources :posts, concerns: [:previewable]
resources :reporters, concerns: [:previewable]
resources :stories, concerns: [:previewable]

Utilities

Prevent the enter key from submitting your forms

On some pages, like New and Edit, we don't want the "Enter" key to submit the form. You can prevent this by adding:

  <script>outpost.Utilities.preventEnterFromSubmittingForm("#edit_blog_entry");</script>

The form ID argument is optional. By default it will target all forms on the page.

More documentation to come.

Contributing

Pull Requests are encouraged! This engine was built specifically for KPCC, so its flexibility is limited... if you have improvements to make, please make them.

Fork it, make your changes, and send me a pull request.

Run tests with bundle exec rake test

Outpost