Build Status

Twice Baked Core

TB Core is the base Twice Baked engine that provides user authentication, roles, and an admin dashboard upon which CRUD apps can be built. Use TB Core to power content-managed websites or as a quick-and-simple way to add a backend to your existing data models.


TB Core 1.2.x and higher requires Rails 4 and is developed using Ruby 2.1. Lower versions of Ruby may continue to work but are not heavily tested.

For Rails 3 compatiblity, stick to TB Core 1.1.x.


  1. In your Gemfile add the following

    gem 'tb_core'
  2. Run bundle install

  3. Run the setup generator to copy in the base templates and migrations

    rails g spud:setup
  4. run a rails server instance and point your browser to /admin

Setup Tasks

The spud:setup generator takes care of a few tasks for you. If you missed that step or would like to complete them manually, they are listed below:

  1. Your base ApplicationController should extend from TbCore::ApplicationController.
  2. Your base application.html.erb should include a few special head tags (see generator template for example).
  3. You should copy in the base migrations with rake railties:install:migrations.


The core engine provides various configuration options.

Spud::Core.configure do |config|
  config.option = value
  • admin_applications: Array of custom admin modules. See section below for more info.
  • site_name: Controls the site name displayed in the dashboard and tb_page_title view helper.
  • from_address: Sender address used for password resets and other mailers.
  • production_alert_domain: When set, displays a prominent warning bar in the admin dashboard directing users to the production domain. You should only set this value in config/environments/staging.rb.

Adding Apps to the Dashboard

Using the Module Generator

The fastest way to add an app to the dashboard is via the geneator. The generator is best suited for situations where you are also creating a new ActiveRecord model, and you want to create a standard set of CRUD actions to manage it.

rails g spud:module Cars make:string model:string year:integer classic:boolean notes:text

Running the example above would create an ActiveRecord model, migration, admin controller with CRUD actions, and a set of front end views.

Adding Manually

Sometimes you want to create a dashboard app without all the magic described above. Start by building your controllers and views under the admin namespace. Extend your controller from Admin::ApplicationController in order to inherit the default admin behaviors, and look at the core admin controllers for example implementations.

Then, add your module to the Spud::Core.config.admin_applications array. We recommend you perform this action in either an initializer or within your application.rb file.

Spud::Core.config.admin_applications += [{
  # Required:
  :name => "Clients",
  :thumbnail => "spud/admin/clients.png",
  :url => "/admin/clients",
  # Optional:
  :badge => ->(user){ Client.where(:is_pending => true).count() }

Restart your application, and the new module should appear on the dashboard.

Extending the User Model

A common requirement is to add custom fields and functionality to the base spud_user model. We have provided several spots where you can do this.

Adding custom methods and attributes to the SpudUser class

Create a file in your app at app/models/spud_user.rb, and build a class called SpudUser that extends from Spud::SpudUserModel. You do not need any explicit requires - the autoloader will take care of the rest.

class SpudUser < Spud::SpudUserModel
  has_attached_file :avatar
  attr_accessible :avatar
  def say_hello
    return "Hello, World!"

Adding fields to the add/edit user form

Create a file in your app at app/views/admin/users/_form_additions.html.erb.

<div class="control-group">
  <%= f.label :avatar, :class=>"control-label"%>
  <div class="controls">
    <%= f.file_field :avatar %>

Adding fields to the user show action

Create a file in your app at app/views/admin/users/_show_additions.html.erb.

<% if @user.avatar_file_name %>
    <%= image_tag @user.avatar.url(:thumb) %>
<% end %>

Error Handling

The base TbCore::ApplicationController will automatically rescue from any Spud::NotFoundError and Spud::AccessDeniedError errors by rendering out the layouts/error_page.html template. If you ran the spud:setup generator, a template of that name will have been created for you automatically.

When building your own apps you may raise a Spud::NotFoundError or Spud::AccessDeniedError error at any time to halt further execution and cause the error page to render. For example:

class CarsController
  before_action :get_record
  # ... actions here
  def get_record
    @car = Car.find_by(:id => params[:id])
    if @car.blank?

Error messages also support localization. Add the following to en.yml, as well as any other locales you are using:

        title: "Not Found"
        message: "The %{item} you were looking for could not be found."
        title: "Access Denied"
        message: "You are not authorized to view the requested %{item}."

Form Builder

The TbCore::FormBuilder class is designed to output a Bootstrap form in the form-horizontal style. Use it to quickly build simple forms with lots of fields. Validation errors will appear automatically next to their input fields.

You can specificy the :builder attribute in a form_for, or you can just use the provided tb_form_for helper to use it automatically.


<%= tb_form_for @widget do |f| %>
  <%= f.tb_text_field :name %>
  <%= f.tb_number_field :age %>
  <%= f.tb_save_buttons('Widget', widgets_path) %>
<% end %>

See form_builder.rb for the full details.

User Select

A common case is to have a model in your app that belongs_to :spud_user. The tb_user_select form helper method makes it easy to assign or create new users from within another form.

<%= tb_form_for @widget do |f| %>
  <%= f.tb_user_select %>
  <%= f.tb_text_field :name %>
  <%= f.tb_save_buttons('Widget', widgets_path) %>
<% end %>

Table Sorting

We provide a few tools to assist with sorting tables of data backed by ActiveRecord queries. In your view, the tb_table_header helper method will generate a <thead> element with header tags.


<%= tb_table_header :users_path do |t| %>
  <%= t.sortable :full_name %>
  <%= t.sortable :email %>
  <%= t.sortable :birth_date %>
  <%= t.header :favorite_color %> # Header without sorting
  <th>Plain HTML is fine too</th>
<% end %>

On the controller side, you can choose to deal with the :dir and :sort params using your own custom logic, or you can use the SortableParams module.

include TbCore::SortableParams

sortable_by :email,                               # A basic sort
            full_name: [:last_name, :first_name], # This will sort on two columns
            birth_date: ' :dir',  # This will sort on a joined table
            default: :email                       # The sort to use when none is passed

def index

Remote Forms

If you choose to include the tb_core JavaScript file in your project, we provide a couple additional features on top of the standard jquery-ujs adapter. Use the following data attributes on your remote forms.

  • data-errors: Set to inline for inline form errors, or alert for a browser alert window.
  • data-success: Set to a path you would like to redirect to. For example, redirecting back to an index after a successful post.

This feature works with tb_form_for or standard form_for forms.


<%= tb_form_for @widget, :remote => true, :data => {:errors => :inline, :success => widgets_path} do |f| %>
  <!--input fields here -->
<% end %>

See remote.js for the full details.

Roles & Permissions

A user can be assigned to a role, which itself has one or more permissions. These permissions serve two distinct purposes: Determining what dashboard apps a user has access to (if any), and being used within your application logic to establish fine-grained user abilities.

Checking permissions is easy. Each permission has a tag which is a unique, namespaced identifier. Use the has_permission? and has_any_permission? methods on the user object to check for their existence. Keep in mind that these calls will always return true for users who have the super_admin flag set.

if !@spud_user.has_permission?('my_website.clients.my_clients')
  redirect_to root_path, :notice => 'Permission Denied!'

Permissions are created one of two ways:

  1. Every dashboard app automatically generates a "Full Access" permission, with a tag of admin.(app name).full_access.
  2. You as the developer may append custom permissions to the Spud::Core.permissions array.

Create custom permissions whenever you need to permit an action that falls outside of the standard "full access to an app" use case; For example, turning on/off the ability for a user to upload an avatar.

// application.rb
Spud::Core.permissions += [
  {:tag => 'my_website.profile.avatar', :name => 'Upload an avatar to my profile'}
// some_view.html.erb
<% if current_user.has_permission?('my_website.profile.avatar') %>
  <%= link_to 'Upload Avatar', upload_avatar_path %>
<% end %>

Finally, custom permissions may optionally be tied to one or more dashboard apps. A user who has the permission shown below would have access to the the Clients and Projects dashboard apps. After that is is up to you to code your view and controller logic in accorance to what permissions the user has.

// application.rb
Spud::Core.permissions += [{
  :tag => 'my_website.projects.project_management',
  :name => 'Manage clients and projects, but cannot delete them or view private info',
  :apps => [:clients, :projects]


Twice Baked uses RSpec for testing. Get the tests running with a few short commands:

  1. Create and migrate the databases:

    rake db:create
    rake db:migrate
  2. Load the schema in to the test database:

    rake app:db:test:prepare
  3. Run the tests with RSpec

    rspec spec

After the tests have completed the current code coverage stats is available by opening /coverage/index.html in a browser.

You can include a few basic test helpers in your own twice baked applications by requiring tb_core/test_helper in your specs.