bhf
Agnostic Ruby on Rails admin interface.
Easy integratable and highly configurable.
Agnostic Ruby on Rails admin interface.
Easy integratable and highly configurable.
A simple to use Rails-Engine-Gem that offers an admin interface for trusted user.
Easy integratable and highly configurable and agnostic.
Works with ActiveRecord
and Mongoid
.
>= 4.0.0
Gemfile
gem 'bhf'
bhf.rb
to your config/initializers/
dir
Bhf.configure do |config|
config.on_login_fail = :login_url
end
(example)
routes.rb
mount Bhf::Engine, at: 'bhf'
(example)
bhf.yml
to your config/
dir
pages:
- my_page: # Page name, not really important for now
- my_model: # replace this with whatever model name you already have in this project (lowcase)
(example)
Now http://localhost:3000/bhf
is available, but you will be redirected back to the config.on_login_fail
url. Because bhf doesn't trust you yet.
User authentication happens completely outside of bhf, this gives you the maximal flexibility to authentication process. The authlogic, device or the good-old http_basic_authenticate_with
or anything else.
Let's keep it simple and use http authenticate, go to your routes.rb
and add this line:
get 'admin', to: 'admin#login', as: :admin
(example)
create a admin_controller.rb
and make sure users that are allowed to open bhf get the session[:is_admin] = true
before they are redirected to bhf.root_url
.
class AdminController < ApplicationController
http_basic_authenticate_with name: 'admin', password: 'bhf'
# somebody how doesn't know the login data can't open the login action
def login
# bhf checks for this session in a before_filter, if it's true user will pass
session[:is_admin] = true
redirect_to bhf.root_url
end
end
(more advanced example with authlogic)
You can change the session name in the config/initializers/bhf.rb
. Because you are in control of when this session variable is set, you are in control what authentication method you are using.
Now hit http://localhost:3000/bhf
again and login!
Along with the standard approach in authentication, instead of defining the #login
action in the
Admin
controller, you are able to redefine Bhf::ApplicationController
, and its #check_admin_account
method, to give to bhf the knowledge about weither current user is admin or not. Do it like follows:
class Bhf::ApplicationController < ActionController::Base
protect_from_forgery
before_filter :authenticate_user! # if devise authentication is used
include Bhf::Extension::ApplicationController
def check_admin_account
# Here expression must be evaluated to `true` if user is admin.
current_user.is_admin?
end
end
After logging in you see the list of pages form bhf.yml
, click one of them and you'll see the list of models (called platforms) this page contains. As you see platforms need only little configuration appear on a page and be ready to use. But of course we often need some basic business logic for example: hiding fields, adding custom fields, removing the delete button or using a curtain scope for the list of entries. All of this and much more can be setup via bhf.yml
. This is how a typical bhf.yml
looks like:
pages:
- statics: # Page Statics
- statics: # bhf can work from this point on because there is a model named Static
- posts: # Page Posts
- posts:
table:
# default_scope hides some posts so we need a different scope
source: all_posts
# columns to display
display: [id, headline, subheadline, content, category, published, published_at]
form:
# fields to display
display: [id, category, author, headline, subheadline, content, published, published_at]
types:
# change content field from a textarea to a markdown wysiwyg
content: markdown
- settings: # Page Settings
- category: # bhf can work from this point on because there is a model named Category
- authors:
table:
# allow to sort the entries via drag and drop (make sure the default_scope order by position)
sortable: position
# hide the delete button
hide_delete: true
form:
# fields to display
display: [id, name, job_title, email]
This are the keys you can add to a platform model
, table
, form
, show
, sortable_property
, hooks
, extend_abstract
table:
(hash) contains all the logic that you can see in the table view. This are the possible hash options:
scope:
(string/array) name of the scope (read more about Rails scopes). default: all
(source:
is an alias)user_scope:
(string) name of the scope that will be applied on the user instance and not directly on the platform's model. default: false
display:
(array) a list that will define the model instance getter and the order of the table rows. default: id
+ 5 more random keys. (columns:
is an alias)exclude:
(array) a list of model instance getters that will not be shown (the opposite of display:
). default: false
types:
(hash) key is the model instance getter or a value defined in the display:
array. value is the name of the variable type or a custom partial name. default: {}
(example)show_duplicate
: (boolean) shows the duplicate entry button. default: false
sortable
: (boolean) show the sort icon (drag and drop). default: false
hide_delete:
(boolean) hides the delete button. default: false
hide_edit:
(boolean) hides the edit possibilities in the table button. default: false
hide_create:
(boolean) hides the create button. default: false
search:
(string/boolean) name of the search method that will be used instead of bhf_default_search
, if set to false
it will remove the platform search form. default: nil
search_field:
(boolean) show the search field. default: true
custom_search:
(string) partial name/path used inside of the search form. default: false
custom_footer:
(string) partial name/path used in the platform footer, this will not remove the pagination. default: false
entries_per_page:
(int) number of entries per displayed on a page. default: false
custom_partial:
(string) partial name that will be used instead of the standard table platform partial. default: false
quick_edit:
(boolean) enables quick edit in the table view. default: false
custom_link:
(string) changes the link of the entry. default: false
hide:
(boolean) hides the platform. default: false
form:
(hash) contains all the logic that you can see in the form view. This are the possible hash options:
display:
(array) a list that will define the model instance getter and the order of the table rows. default: all model fields.exclude:
(array) same as table.exclude:
types:
(hash) same as table.types:
. See form field types for the available types or define your own.links:
(hash) key is the model instance getter or a value defined in the display:
array. value is the name of the reflection platform linked to this key. Having false
in the value will hide the link, because sometimes bhf is able to find reflection platform without your configuration. default: {}
multipart:
(boolean) form is multipart. default: false
show:
(hash) contains all the logic that you can see in the show view. But first you need to link a entry to the show view.
pages:
- page:
- authors:
custom_link: entry_path
This are the possible hash options:
show_extra_fields:
(array) appends custom fields at the end of the view. default: false
display:
(array) a list that will define the model instance getter and the order of the table rows. default: all model fields. (definitions:
is an alias)exclude:
(array) same as table.exclude:
types:
(hash) same as table.types:
model:
(string) actual model name (e.g. User
) it's often not needed because bhf tries to find the model via the platform name, but sometimes the have to be different.
pages:
- page:
- best_authors:
model: authors
sortable_property:
(string) name of the property that needs to get updated via ajax on entries sort. default: position
hooks:
(hash) contains the hooks that will be used while saving the entry
after_load:
(string) method name that is called on a entry instance right after was loaded. default: false
before_save:
(string) method name that is call before saving entry instance with the Rails params
hash. default: false
after_save:
(string) method name that is call after saving entry instance with the Rails params
hash. default: false
There are some ActiveRecord
hooks that do the same but hey will not let you access the http params
.
extend_abstract:
(string) name of the abstract platform. Read more about abstract platform settings.
Bhf.configure do |config|
end
This are the possible hash options:
config.css
: (array) all the css file links. default: ['bhf/application']
config.js
: (array) all the js file links. default: ['bhf/application']
config.abstract_settings
: (array) link to abstract .yml
files. default: []
config.on_login_fail
: (symbol) url bhf redirects to if session[config.session_auth_name]
isn't true
default: :root_url
config.session_auth_name
: (symbol) session key used to look up in user's session whether it's true
or not default: :is_admin
config.session_account_id
: (symbol) session key used to look up in user's id
which will be passed to the config.account_model_find_method
method default: :admin_account_id
config.account_model
: (string) user's model name default: 'User'
config.account_model_find_method
: (string) user's model method used to find the current user instance default: 'find'
config.logout_path
: (symbol) link used in the footer if the user was found via config.account_model
and config.account_model_find_method
default: :logout_path
config.paperclip_image_types
: (array) default: ['image/jpeg', 'image/pjpeg', 'image/jpg', 'image/png', 'image/tif', 'image/gif']
(example)
If you wish to display different pages or/and platforms to your users you can do that with roles support that is backed into bhf.
First of all you will have to provide a session[:admin_account_id] = @user_session.user.id
.
After finding the user bhf will try to retrieve user's roles. If the getter bhf_roles
is defined bhf loops through the roles and collects "bhf/#{role.identifier}.yml"
settings. So you no longer have the config/bhf.yml
, instead you will have something like this: config/bhf/admin.yml
, config/bhf/editor.yml
. If you have same page names in each roles settings file defined, bhf will merge them. You can't define same names for platforms. If you need to reuse the settings form a platform consider using abstract settings.
schema.rb
create_table "roles", force: true do |t|
t.string "identifier"
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "roles_users", id: false, force: true do |t|
t.integer "role_id"
t.integer "user_id"
end
user.rb
class User < ActiveRecord::Base
...
has_and_belongs_to_many :roles
...
def bhf_roles
roles
end
end
Possible user model instance methods:
bhf_roles
: returns: (array) a list of roles objects with at least a identifier
key.Reusing platform settings is simple. Add config.abstract_settings = ['abstract']
to your bhf initializer and add config/bhf/abstract.yml
. Define the abstract platform settings the way you normal would, all the pages and platforms wont be displayed. Use the extend_abstract:
platform's setting to link the platform to an abstract platform.
Example of a abstract settings .yml
file and how it can be used.
Table:
Available types for model columns
:
array
(string)boolean
(string)date
(string)extern_link
(link to string)toggle
(boolean) provides a link for an instant change of the boolean valuefile
(link to file)hash
(string)image
(image_tag)number
(string)paperclip
(file name string or image_tag)carrierwave
(image_tag)type
(string)primary_key
(strong_tag)string
(string)text
(string)Available types for belongs_to
, has_many
, has_one
, embeds_many
,
embeds_one
and has_and_belongs_to_many
:
default
(string)Form:
Available types for model columns
:
array
(multiple text_fields)boolean
(check_box)date
(date- and/or time-picker)hash
(string)mappin
(google-maps map with a pin)markdown
(markdown editor)multiple_fields
(multiple text_fields)number
(text_field)paperclip
(file_field)password
(password_field)static
(string)string
(text_field)type
(select_tag)image_file
(file_filed with a image preview)text
(text_area)wysiwyg
(wysiwyg html editor)Available types for reflection belongs_to
:
select
(select_tag)radio
(radio_button)static
(string)Available types for has_and_belongs_to_many
and has_many :through =>
:
check_box
(check_box)static
(string)Available types for has_many
, has_one
, embeds_many
and embeds_one
:
static
(string)This a example of how to set up a markdown editor.
Add a logo_bhf.png
or logo_bhf.svg
file to your app/assets/images/
directory.
bhf includes the following methods into ActiveRecord::Base
and Mongoid::Document
Instance methods:
to_bhf_s
returns: (string) Finds a good looking title for a instance, e.g.: Post.first.to_bhf_s #=> 'Koala bears are happy again' # title attribute found
or Roster.first.to_bhf_s # => 'Roster ID: 33' # title attribute not found
override this method in your models when needed.Possible instance methods:
before_bhf_duplicate(old_object)
: returns: (doesn't matter) Is called right before the duplicated of an entry happens. This method is called on the new instance and sends the old instance as an argument.after_bhf_duplicate(old_object)
: returns: (doesn't matter) Same as before_bhf_duplicate(old_object)
only that it's called after save
on the new instance.bhf_can_edit?
: returns: (boolean) Finds out whether the shortcut edit link can be displayed for the current user. Called inside the bhf_edit
view helper method.to_bhf_hash
returns: (hash) Hash that gets merged with the default hash of the model attributes after a quick edit successfully returns a json which will be used to update the html.Class methods:
bhf_default_search(search_params)
returns: (ActiveRecord::Relation) This method is used to get search results for the search form. Override this method to perform better searches with your business logic.bhf_attribute_method?(column_name)
returns: (boolean) Checks if column_name
is a column.bhf_primary_key
returns: (string) Key name of the primary key attribute, relevant for mongoid.bhf_embedded?
returns: (boolean) Is this instance embedded, relevant for mongoid.bhf is a Rails-Engine this means you can use hierarchical overrides, simple include a view in your app in the same directory and bhf will render this view instead of it's own. Check out bhf's views directory and this example where the footer partial is overridden. You can also override the layout or use it in your own controllers (e.g. for login).
It's very easy to add new custom views and assets or to overwrite the existing ones with your own.
Notice the demo_checkbox
string inside the display:
array:
pages:
- posts: # Page Posts
- posts:
form:
display: [id, name, demo_checkbox]
Add a partial to views/bhf/form/column/_demo_checkbox.haml
or .erb
(not _demo_checkbox.html.haml
)
Use the node
view helper to create a label and a div holder for your form elements
= node f, field do
= f.hidden_field :test
= field.name
include this line to your config/initializers/bhf.rb
config.css << 'my_custom_bhf'
add this file to your asset pipeline directory and add it to your config.assets.precompile
array
That's it, you can now work on your css to overwrite bhf default styling or add new styling for your custom views.
include this line to your config/initializers/bhf.rb
config.js << 'my_custom_bhf'
add this file to your asset pipeline directory and add it to your config.assets.precompile
array
Always use the 'bhfDomChunkReady'
not just 'domready'
. Never try to grab DOM elements from the document directly, use scope
as your element scope. Often times only small parts of the DOM get updated via ajax. bhf uses the mootools javascript framework, if you need jQuery feel free to require it via //= require jquery
. Also bhf uses Turbolinks, be aware of that.
window.addEvent('bhfDomChunkReady', function(scope){
console.log(scope);
});
Look at this examples to get a better understanding: Javascript, View)
bhf is not designed to be flexible when it comes to controller logic. While for saving the data to your model you can use simple, yet powerful hooks:
hash settings in the .yml
file for some additional logic you will have to do everything on your own. Here is an example of how you could inject your own controller.
Make sure you have a stadium
and a tournaments
platform defined in your settings .yml
.
Add this code to the top of your routes.rb
:
Bhf::Engine.routes.draw do
patch 'stadium/entries/:id', to: 'tournaments#stadium_update', defaults: {platform: 'stadium'}
get 'tournaments/entries/new', to: 'tournaments#new', defaults: {platform: 'tournaments'}
end
Add a app/controllers/bhf/tournament_controller.rb
file:
class Bhf::TournamentsController < Bhf::EntriesController
def new
@object.save
redirect_to edit_entry_path(@platform.name, @object)
end
def stadium_update
...
end
end
Look at the Bhf::EntriesController
file and try to reuse as much as possible in your controller.
bhf right now offers I18n support for those languages, you can easily add more languages translating this yaml file and this javascript file (please submit it also as a fork if you have added a language). Make sure you have a fallback included in application.rb
if you don't use en
as your main language. For the translation of the model attributes, bhf heavily relies upon build-in Rails I18n model API.
config.i18n.default_locale = :de
config.i18n.fallbacks = [:en]
Make sure a default time_zone
is set in application.rb
otherwise you are saving dates in GMT.
config.time_zone = 'Berlin'
This a button you can place anywhere in your app to offer the user a shortcut to the bhf's edit entry form.
bhf_edit(model_instance, options = {}, &block)
returns: (nil
or a rendered partial 'bhf/helper/frontend_edit'
)
model_instance
(ActiveRecord instance) required. The model instance you want to link in the shortcut.
options
(hash) can have this options, often times you don't have to set them:
platform_name:
(string) optional. Name of the platform you want to link to.area:
(string) Name of the area.bhf_can_edit?
method.&block
(block) optional. Block of html will be rendered inside of the link
View the code example or the live demo of the icon on the bottom right (if you can't see it your aren't logged in).
This this bhf_pen.svg image and this SASS snippet might be useful.
.bhf_edit
display: block
float: right
width: 40px
height: 40px
text-indent: 100%
white-space: nowrap
overflow: hidden
background: image-url('bhf_pen.svg') no-repeat center center
background-size: 80%
border: 2px solid transparent
border-radius: 5px
padding: 5px
&:hover
border-color: #38A5E9
quick_edit
quick_edit is a sidebar view that contains a form to create or edit a entry. E.g. if you see a <select>
element for a belongs_to
relation you are also presented with buttons to edit the current selected entry or to create a new one, without leaving the current form. This happens if bhf manages to link the relations name to a platform.
Check out the quick_edit features in this demo app (username: "admin" password: "bhf").
If the platform exists and you still don't see the quick_edit features setup a links:
hash in your .yml
settings.
pages:
- settings:
- authors:
form:
display: [_id, name, job_title, email, categories]
links:
categories: best_categories
- best_categories:
model: Category
Areas divide the tabs you can see at the top into different page parts. Having multiple areas is useful if you want to have several totally different navigations and/or platforms in one bhf project. Areas behave a little like roles, if bhf finds a getter bhf_areas
on the user it will loop through the areas.
routes.rb:
mount Bhf::Engine, at: 'bhf/:bhf_area', as: :bhf, defaults: { bhf_area: 'main' }
get 'bhf', to: 'application#redirect_to_users_bhf_area', as: :bhf_help
schema.rb:
create_table "areas", force: true do |t|
t.string "identifier"
t.string "name"
# link to your main app e.g.:
# this area is about oranges and your site has a url http://myfruits.com/oranges ,
# a good link would be '/oranges'
t.string "link"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "areas_users", id: false, force: true do |t|
t.integer "area_id"
t.integer "user_id"
end
user.rb:
class User < ActiveRecord::Base
...
has_and_belongs_to_many :areas
...
def bhf_areas
areas
# You also can use OpenStruct here if you want to avoid an Area model
# [
# OpenStruct.new(identifier: 'main'),
# (OpenStruct.new(identifier: 'admin') if roles.include?(Role.find(1)))
# ]
end
end
application_controller.rb:
class ApplicationController < ActionController::Base
# this simply makes sure you still can use /bhf as an url
def redirect_to_users_bhf_area
r = if current_user
current_user.bhf_areas.first.identifier
else
'main'
end
redirect_to bhf.root_url(bhf_area: r)
end
end
Possible user model instance methods:
bhf_areas
: returns: (array) a list of area objects with at least a identifier
key.bhf_area_roles(current_area)
: returns: (array) a helper to remove roles for current_area
. Return a array of roles objects that you want to use in this area.It's possible to write your own plugins for bhf. Here is an example of how to do it. If you have multiple projects running with bhf, writing a plugin to keep your code DRY makes great sens. Also please let me know about your plugin, so I can list it here.
List of available bhf plugins:
My name is Anton Pawlik, @antpaw, anton.pawlik@gmail.com.
Feel free to ask questions, contact me for support or donate to the project.