I want to re-skin ActiveAdmin using a Bootstrap template theme. However I need to change the layout of the page to suit.
Is it possible to override the layout of ActiveAdmin to suit what I want? It looks different to normal rails conventions - I'd rather just accomplish it using a regular template and then yield the parts of the content that I need in the order that I need them.
Ive done something similar before. Check out this Gist https://gist.github.com/bigfive/6017435
Essentially you patch the active admin base controller to use your new layout by overriding their :determine_active_admin_layout method
# config/initializers/active_admin_patch.rb
module ActiveAdmin
class BaseController
def determine_active_admin_layout
'active_admin_reskin'
end
end
end
Then in your active_admin_reskin layout you can call methods on the Arbre view_factory like so
view_factory[#{params[:action]}_page"].new(Arbre::Context.new(assigns, self)).send(:build_header)
In the gist(https://gist.github.com/bigfive/6017435) Ive made a little view helper for making it easy to call those methods.
Have a look through the active admin source code to see which methods are available to you on various Arbre documents, especially here: https://github.com/gregbell/active_admin/blob/master/lib/active_admin/views/pages/base.rb
Once the markup is changed the way you like, you can #include 'bootstrap' into your active_admin.css.scss file that the generator created and go nuts.
A simple answer would be putting
#import "bootstrap";
into active_admin.css.scss
Officially, its not supported for now https://github.com/gregbell/active_admin/issues/1027
Depending on what version of AA you are using.
(0.6.0 < commit:ec9996406df5c07f4720eabc0120f710ae46c997):
Include your scss, but encapsulate your css selectors in the body.active_admin group. Furthermore, specificity is important, so if you want to override the default styling of AA, you may need to ensure you are overriding the full selector to get the behavior you want.
If you want to find these styles to override them, take a look at AA's stylesheets to see how they style the site be default. Simply include your custom styling after the the AA stylesheet is included.
After commit:ec9996406df5c07f4720eabc0120f710ae46c997
The namespacing of stylesheets has been removed recently, and I have not throroughly tested the implication yet.
BigFive's accepted answer worked for me at the beginning but then produced some bugs when rendering custom partials or when rendering errors in forms.
Inspired by his approach I switched to overriding the individual methods that AA uses to dynamically generate the layout (as AA doesn't use a static layout file that can be easily modified).
You can find the available methods in the source code, but it's pretty self-explanatory and begins in the html element.
Example:
To add some classes and reorganize the elements:
You can put your style in:
assets/stylesheets/active_admin.css.scss
And your html description in:
config/initializers/active_admin_patch.rb:
module ActiveAdmin
module Views
class Header
alias_method :original_build_site_title, :build_site_title
alias_method :original_build_global_navigation, :build_global_navigation
alias_method :original_build_utility_navigation, :build_utility_navigation
def build_site_title
div class: "side_bar_top" do
original_build_site_title
end
end
def build_global_navigation
div class: "side_bar_content" do
original_build_global_navigation
end
end
def build_utility_navigation
div class: "side_bar_footer" do
original_build_utility_navigation
end
end
end
module Pages
class Base
alias_method :original_build, :build
# This should be the same as add_classes_to_body but for the html main element
def add_classes_to_html_tag
document.add_class(params[:action])
document.add_class(params[:controller].gsub('/', '_'))
document.add_class("active_admin")
document.add_class("logged_in")
document.add_class(active_admin_namespace.name.to_s + "_namespace")
end
def build(*args)
original_build
add_classes_to_html_tag
end
def build_page
within #body do
div id: "wrapper" do
div id: "details_view" do
build_title_bar
build_page_content
#build_footer
end
div id: "master_view" do
build_header
end
end
end
end
end
end
end
end
Related
I'm trying to implement Decorators using the learnings from "Rails 4 Patterns" Code School course, but I'm running into trouble as I need a view helper in the Decorator class.
I want my view to have:
<%= #model_decorator.previous %>
Then in the decorator:
def previous
if object.prev_item.nil?
"Previous"
else
link_to("Previous", object)
end
end
The course suggests you make a call to the decorator within your view helper in the view file itself, but that's no good if the logic could output one result with a helper and one without. (i.e. need the output to be a link or not).
I've tried using helpers.link_to but it errors out as not providing the correct information for the url_for option. I've confirmed link_to("Previous", object) works within the view itself.
For Rails 4
include ActionView::Helpers::UrlHelper
link_to("Previous", Rails.application.routes.url_helpers.send("#{object.class.name.underscore}s_path".to_sym, object))
As for me it`s better to make a decorator for it:
class LinkDecorator
include ActionView::Helpers::UrlHelper
def initialize(label, object)
#label = label
#object = object
end
def show
link_to(label, url_helpers.send("#{object.class.name.underscore}s_path".to_sym, object))
end
def index
link_to(label, url_helpers.send("#{object.class.name}s_path".to_sym))
end
...
private
attr_reader :label, :object
def url_helpers
Rails.application.routes.url_helpers
end
end
Example usage:
LinkDecorator.new(object.name, object).show
If I understand your problem correctly, you essentially want links in a plain old ruby object.
My solution would be this:
include ActionView::Helpers::UrlHelper
link_to("Previous", Rails.application.routes.url_helpers.objects_path(object))
# assuming the object is always of one class
If the object is of a different class, than it would be possible to use the .send method to send the correct message to app ie.:
include ActionView::Helpers::UrlHelper
link_to("Previous", Rails.application.routes.url_helpers.send("#{object.class}s_path".downcase.to_sym, object))
# I'd create a function out of that line to make it a bit neater
It sounds like the error thrown by url_for comes from missing the routes and there's a few ways to include those. My solution kinda avoids that problem by using Rails.application.routes.url_helpers. Hope this helps!
I am trying to make an ActiveRecord call to get information for the application layout (the default application.html.haml layout). I know I am not supposed to put logic into the layout itself, but I am unsure of where to put it.
The line of code I need run in the layout is just a simple Model call:
Client.find_by(:id => current_user.client_id)
I would suggest throwing it in helpers/application_helper.rb. I've used this in the past for things such as title helpers and body class helpers.
# helpers/application_helper.rb
module ApplicationHelper
def body_class
[controller_name, action_name].join(' ')
end
end
# views/layouts/application.slim
body class=body_class
= yield
The ApplicationController isn't for such helpers. It's mainly as support for your controllers, not your views.
It's okay if you put it in ApplicationController. And you can put controller related code to controllers/concerns folder.
'concerns/concern.rb':
module Concern
def method
# Your code here
end
end
To use a module from concerns folder include it in the controller: include Concern
I've tried Facebook's Open Graph protocol in adding meta data on Rails pages. What I want to do now is to make my code not duplicated or D.R.Y.---instead of putting one meta-data header for each controller page I have, I'd like to create a base class called "MyMetaBuilder" which will be inherited by the sub-pages, but don't know where and how to start coding it...
Someone suggested that meta data property values must be dynamically generated depending on the context. For example, PlayMetaBuilder, CookMetaBuilder and so on...
Also, when unit testing the controller action, how do I verify for its existence?
Thanks a lot.
One thing is defining the tags, another is rendering them. I would do the following:
write a controller mixin (something like acts_as_metatagable) where I would define specific fields for each controller (and populate the remaining with defaults). These would be assigned to a class (or instance) variable and in this way be made accessible in the rendering step).
write an helper function which would take all my tags and turn them into html. This helper function would then be called in the layout and be rendered in the head of the document.
so, it would look a bit like this:
# homepage_controller.rb
class HomepageController < ActionController::Base
# option 1.2: include it directly here with the line below
# include ActsAsMetatagable
acts_as_metatagable :title => "Title", :url => homepage_url
end
# lib/acts_as_metatagable.rb
module ActsAsMetatagable
module MetatagableMethods
#option 2.2: insert og_tags method here and declare it as helper method
def og_metatags
#og_tags.map do |k, v|
# render meta tags here according to its spec
end
end
def self.included(base)
base.helper_method :og_tags
end
end
def acts_as_metagabable(*args)
include MetatagableMethods
# insert dirty work here
end
end
# option 1.1: include it in an initializer
# initializers/acts_as_metatagable.rb
ActiveController::Base.send :include, ActsAsMetatagable
# option 2.1: insert og_metatags helper method in an helper
module ApplicationHelper
def og_metatags
#og_tags.map do |k, v|
# render meta tags here according to its spec
end
end
end
What I did for Scoutzie, was put all metadata into a head partial, with if/else cases as such:
%meta{:type => 'Author', :content => "Kirill Zubovsky"}
%meta{'property' => "og:site_name", :content=>"Scoutzie"}
-if #designer
...
-elsif #design
...
-else
...
This way, depending on the variables that load, I know which page it is, and thereby know which metadata to include. This might not be an elegant solution, but it works and it's really simple.
I have a kind of social network website.
I have a logic to create the path for the user, and select an avatar for each user described in UsersHelper methods user_path(user) and user_avatar(user).
Instead I want to have methods like user.path and user.avatar, but I don't want to have that code inside the model file.
I tried extending the User class inside the helper:
module UsersHelper
class User
def avatar
...
end
end
end
That doesn't work - the method I added aren't recognized (I'm guessing Rails dynamically generates the ActiveRecord class on demand so my methods don't apply?)
I'd really appreciate ideas
First, there's a reason helpers are separated from models. According to the MVC pattern your model shouldn't know anything about your helpers, and not vice versa either (the latter is usually ignored in Rails, though).
But, if you want to do this, you need to do class ::User in model UsersHelper, or the following:
module UsersHelper
end
class User
end
The reason for this is that Ruby namespaces classes. So you defined UsersHelper::User, while your model is called User. Calling the class ::User or defining it outside the module forces it into the root namespace.
However, as I said, this is not the "correct" way to do it. A better way would be how you're already doing it, or maybe using a decorator pattern.
Draper is an awesome little gem that does an excellent job of achieving the functionality you're looking for (adding view / presentation specific code while still making it "feel" like the model you're working with). We've removed almost all of our model-specific helpers after starting to use draper.
Basically, it works by defining decorators, which work like a wrapper around your model. A decorator looks and feels like the model it's decorating, but can have additional functionality defined on top of it. In addition, you can restrict access to certain fields and lock stuff down if you like.
For your example, adding a decorator would be as simple as:
(in app/decorators/user_decorator.rb)
class UserDecorator < ApplicationDecorator
decorates :user
def avatar
# your implementation
end
(in your controller)
def index
respond_with UserDecorator.decorate(User.all)
end
(in your views)
<div class='avatar'><%= user.avatar %></div>
<div class='name'><%= user.name %></div>
Helpers are intended to use with the views, not with the models.
If you wish to have something like user.avatar, you have to add it to your model.
If you want to stick it in the helpers, then in the UsersHelper add a user_avatar method.
Ive made a nice editor in jQuery and i want to add it as a form helper method.
how would i go about making a new form helper method?
ideally id like to be able to call:
f.nice_editor :field
Part of the question is: where do you put the nice_editor code? I don't think it is a good idea to directly edit class ActionView::Helpers::FormBuilder in your installation. Instead, put your code in one of the files in app/helpers. There are several ways to add extension methods to FormBuilder.
For instance, suppose you have a helper file items_helper.rb:
module ItemsHelper
# this is one way to define new instance methods
ActionView::Helpers::FormBuilder.class_eval do
def nice_editor(conf,*opts)
...
end
end
end
Also, see this good discussion, which shows how to use self.included() to extend FormBuilder.
The object yielded to form_for is an instance of ActionView::Helpers::FormBuilder. So all you have to do is to add instance methods there.
class ActionView::Helpers::FormBuilder
def custom_field(...)
...
end
end
after some research i found that this works:
class ActionView::Helpers::FormBuilder
def nice_editor(conf)
#stuff to draw the editor
end
end
"conf" would have all the symbol options passed to it from the view. it works fine with f.