ActiveAdmin: Adding pages dynamically to the menu after the application has started - ruby-on-rails

I'm using ActiveAdmin as a CMS. I want my users to be able to create some pages and then these pages should be added dynamically in the active admin menu under a parent page called "Help". However, I can't workout how to register newly created pages, since the register_page method only runs when the server is started.
Is there a way to dynamically build the path to newly created pages? Or is there a way to run register_page dynamically (perhaps in a proc?) without having to restart?
Later edit:
The problem is the fact that I don't have a route for the newly created page so I get an error saying that admin_page_title_path could not be found after I create the page. This is because I'm doing a register_page in an after_create:
ActiveAdmin.register Page do
after_create do |page|
ActiveAdmin.register_page page.title do
menu :parent => 'Help'
content :title => proc { I18n.t("active_admin.manual") } do
columns do
column do
panel "#{raw(page.title)}" do
render :partial => "admin/pages/content", :locals => {page: page}
end
end
end #columns
end
end
end
end
If I use Rails.application.reload_routes! then the route is built, BUT this also clears my menus and I basically get a list of ALL the pages in the place where the menu used to be.
All the methods for rebuilding the menu that I tried seemed a bit hack-ish. So I assume there should be a way to rebuild just that one route instead of meddling with resetting resources and re-adding menu items.

Related

Dynamic routes on runtime in Rails

I'm developing a site using refinery. Now for one specific page that is created in the back-end of refinery, i want to use my own controller and views. All the User can do with this page is to set the menu-position, title, meta-info etc. The URL for this page has to look the same as all the other pages.
So for example, the menu structure looks like:
menux
menu1
menu2
specific page
menux
And the URL for "specific page" looks like "locale/menu1/menu2/specific page"
The site is available in multiple languages, so i have to create these routes for all languages.
Currently i'm creating the routes like this:
specific_page_id = 1
Refinery::I18n.frontend_locales.each do |lang|
slugs = []
page = Refinery::Page.find_by_path_or_id(nil, specific_page_id)
# get slug for page in current language
slugs << page.translations.select { |p| p.locale == lang }.first.slug
# get all slugs from parrent pages
while !page.parent_id.blank?
page = Refinery::Page.find_by_path_or_id(nil, page.parent_id)
slugs << page.translations.select { |p| p.locale == lang }.first.slug
end
match "/:locale/#{slugs.reverse.join("/")}" => "controller#action", :via => :get, :constraints => { :locale => /#{lang}/ }
end
With this, i'm getting a route to the specified page in every language like described above.
But the problem is, when the user changes the name of the page or the position in the menu, the routes have to be generated again, which isn't done too often.
Now my question is, how can i do this more dynamically on run-time? I've read a bit about constraints but i don't know if this is what i need.
Thanks for your help!
I needed to figure out building routes off a database model myself in a Rails 4 application (which is called "ComingSoon" in the examples below. I wanted pages that could be edited on the back-end and given a user-friendly name, which is stored in the Page#name field. So "About Us" titled page typically becomes "about_us" name, which leads to "http://localhost:3000/about_us" The following is the technique I came up with:
Create a new model in app/models/dynamic_router.rb
class DynamicRouter
def self.load
ComingSoon::Application.routes.draw do
Page.all.each do |pg|
get "/#{pg.name}", :to => "pages#show", defaults: { id: pg.id }, as: "pages_#{pg.name}"
end
end
end
def self.reload
ComingSoon::Application.routes_reloader.reload!
end
end
The key above is that I pass the page's id as one of the parameters, so look up is still on the Page#id field, which is, IMHO, a lot better than using the friendly plugin or lookups on slugerized values.
Add the following line to your config/routes.rb
ComingSoon::Application.routes.draw do
# ...
DynamicRouter.load
end
Finally, when the Page is updated, we need to reload the routes, so add an after_safe callback on the Page model:
class Page < ActiveRecord::Base
after_save :reload_routes
def reload_routes
DynamicRouter.reload
end
end
I plan to refine this further to only reload routes if the name attribute is changed and perhaps simply edit the existing route rather than reloading everything if performance proves to be an issue (which at the moment, its not).

ActiveAdmin display default view content

I am working with ActiveAdmin and need to make customizations to some views and have come across a couple of scenarios I feel I am doing wrong.
I am adding an additional table to a show view (comments on Posts). This requires me to rewrite the whole attributes table and then add my panel. Is there a way to customize views without losing the default content?
I would also like to add a table of associated items on the show view which doesn't need to be customized is there any way to include the default tale that would normally be on the index view with default actions and paging?
After digging in the source code of Active Admin, I've found a way to patch this
show do
default_main_content
panel "Your Added Stuff" do
# Add stuff here
end
end
Of course this is undocumented and maybe considered a hack, but unless any other solution exists, it works.
Note: To do this in the form action (new and edit):
form do |f|
f.inputs
# Other inputs here
f.actions
end
Instead of using default_main_content, you could also just loop through the columns on the model like so:
ActiveAdmin.register Ad do
show do
attributes_table do
default_attribute_table_rows.each do |field|
row field
end
# Custom bits here
end
end
end
A couple areas of the documentation might help you:
See Customize the Show Page, Customizing the Index Page, Customizing the Form, and Custom Pages. An example of customizing a show screen:
ActiveAdmin.register Ad do
show do |ad|
default_main_content
h3 ad.title
end
end
See Custom Action Items in the Custom Controller Actions section of the documentation. An example:
action_item :only => :show, :if => proc{ current_admin_user.super_admin? } do
"Only display this to super admins on the show screen"
end
NB default_main_content does not exist in the documentation anymore, yet it works fine.
Just figured that out myself:
For the default table index page you can do something like this
index do
h1 "Hello World"
p "get more content"
instance_eval(&default_table)
end

What is the "Rails Way" to load templated pages

I am just starting to pick up Ruby on Rails. I rather like it, but I ran into a roadblock. I know of two ways to solve a problem but I am curious as to what the "Rails Way" to do this is.
The Setup
I have an index page that lists project descriptions. When a user clicks on a project it brings them to the show function of the projects controller. Projects in the list are loaded from a MySQL database.
What I Want To Do
I want to be able to load project specific information on each "Project Page." This information consists of documentation, code examples, etc. Each page will have the same general template.
Way 1
Store the HTML and text in the MySQL database in a "Sections" table and load all the sections related to that project. Display each section going down the page.
Way 2 (The way I would rather do it)
Have a separate view for each project with the full documentation. Load a specific view based on the project that is loaded in the show function
Comments
Is there some third way in Rails that I am not used to thinking of since I come from using CodeIgniter?
I am completely up for adapting to what the "Rails Way" is, but I am just not sure what the proper convention for this kind of thing is.
Or is this problem a case of, it does not matter how you do it at all?
Thanks in advance.
There are many ways to do this, one of the easiest ways is to override default template.(render :action doesn’t run any code in the target action only template)
This is Way2 in your question. Example:
def show
#project = Project.find(params[:id])
if #project.has_template?
render :action => "show_#{#project.template_name}" and return
end
render :action => "show"
end
In this example directory app/views/projects should have templates for each project
with names like "show.html.erb" for default one and "show_myspecialproject.html.erb" for project with template_name "myspecialproject" , etc....
Template_name is a method that tell you if project has such or should use default, you can put any logic in this method, it can be additional column id table or just template_name can be equal to project name, or it can just check if file exist in current directory.
You can also use partial templates if you want to use show.html.erb because you have code duplications and keep your templates DRY.
http://rails.rubyonrails.org/classes/ActionView/Partials.html
This way
controller action is default
def show
#project = Project.find(params[:id])
end
in show.html.erb
<h1><%= #project.name %></h1>
<%= render #project.tamplate_name %>
In this example all partial templates should start with "_" ,
Ex. : "_myspecialproject.html.erb"
If each project has the same general template, I don't know what the value is of having different views.
Here's a suggestion to try on for size.
You can store the documentation, code examples, etc. in a sections table, or each type in it's own table. If you have them in a sections table, include a type field, and use different classes for each type. Let's say Documentation and CodeExample are two such classes.
Define a has_one in your Project class for each type with a :conditions option.
class Project < ActiveRecord::Base
has_one :documentation, :conditions => { :type => "Documentation" }
has_one :code_example, :conditions => { :type => "CodeExample" }
end
class Documentation < ActiveRecord::Base
set_table_name "sections"
belongs_to :project
end
If you want to organize your project show page to include the sections, you can call partials for each section type.
If you want your project show page to have a link or tab (you can implement tabs on the page using links or CSS formatted buttons) go to a show action in a controller for the section type. Using nested routes for this might be an idea you want to use.
resources :projects do
resource :documentation
resource :code_example
end
These are some ideas. You might take what you want from my ideas, if you don't want to do it all this way.

View show page for menu item in Active Admin

I would like to dynamically change the menu based on the permissions of the viewing user. I would like the superadmin user to have access to the normal Resource actions (index, show, update, etc). So when an admin clicks on a menu item, it would take them to the index of that resource. I would like to restrict the normal admin user to just viewing a specific show page.
The menu route for the superadmin would be: /admin/resource
The menu route for the normal admin would be: /admin/resource/id
I would also like to restrict normal admin access to the index view, or other resources that they don't have access to. I have been able to achieve both these things, but I have yet to be able to map a menu item to a specific show page. I know I could create a custom page and view, but I really would like to share the custom DSL for the show and edit pages between superadmin and the normal admin.
Anyone know how to make this happen.
Ok, so I figured a way to get what I want. I am not sure if exactly fulfills what I wanted. (meaning, it would be nice to create custom menu items that are mapped to specific resources)
I just overwrote the index controller action to redirect to the specific show page. Because the super admin needs access to the original Store resource, I had to alias it with :as.
ActiveAdmin.register Store, :as => 'My Store' do
menu :if => proc{ !current_user.is_admin? },
:label => 'My Store'
actions :show, :edit, :update
controller do
def index
redirect_to(admin_my_store_url(current_user.store))
end
end
end

Rails3 - routing: How do you create a root path for a database-driven item?

Background info
I have an app where users can make microsites ala http://myname.the_app.com.
Microsite has_many :pages
Each Microsite belongs_to :landing_page (which is a Page) that determines where the visitor is fowarded when they view the site. Right now the app does a redirect_to #microsite.landing_page. The visitor then sees a url like so: `http://myname.the_app.com/pages/id
How can I render the landing page but keep the root path? I want to be able to see the "about page" but with the url http://myname.the_app.com.
An option
Here's an option: have a home_controller#index action that instantiates all the page variables it needs. However... I'm holding this off because what if I have more than page? What if I want the landing_page to be something else in the future, like a contact_form located at `/contact_forms/id?
Thanks!
Perhaps render :template => "pages/#{whatever_id}" might solve your problem. Just replace your redirect_to with the render...

Resources