Accessing model data from numerous nested layouts in Rails - ruby-on-rails

Let's say I have a nested layout in Rails, as described in Rails Guides, where my application.html.erb file has:
...
<%= content_for?(:content) ? yield(:content) : yield %>
...
somewhere in it.
In both the application layout, and in the sub-layout, I need to access data from models.
I've found a solution from this question. I could put something like this in my ApplicationController:
class ApplicationController < ActionController::Base
before_filter :get_main_layout_stuff, :get_sub_layout_stuff
private
def get_main_layout_stuff
#cart = find_cart
end
def get_sub_layout_stuff
#categories = find_categories
end
end
and then in any controllers not using the sub layout, I can just say:
skip_before_filter :get_sub_layout_stuff
This works fine. However, if I start to have more layouts, say, a dozen, with many layers of nesting, and where maybe a layout needs specific information based on the contents of the URL, then it becomes unwieldy. I have to either list a million skip_before_filters in every controller, or I have to remember exactly which set of functions to add as a before_filter in each controller. Neither solution is very DRY, when I'm already specifying which layout I'd like in each controller.
So my question is: how can I get the right information to each layout in the hierarchy of layouts without having a crazy number of before_filters? Is there way to automatically load the required data based on the layout requested and then recursively go back to load the required data for each of parent layouts? Or maybe is there a way to have a "controller" for each layout that gets called whenever the layout is required? Or am I thinking about this problem in completely the wrong way?

Related

rails overriding/scoping views for multi tenancy app

I'm wondering how I can scope my views. I want to have custom themes depending on the organization, I can use render directly on the show/index actions... and that works, but I would have to override a lot of actions on my application. I would prefer to do it on the controller level and tried doing it with prepend_view_path but it didn't except the variable as undefined.
class EditionsController < ApplicationController
helper_method :current_organization
prepend_view_path "app/views/#{current_organization.slug}/editions" #doesn't work
def show
#edition = Edition.find(params[:edition_id])
#page = #edition.pages.first
render template: "#{current_organization.slug}/editions/show" #works
end
Any ideas?
Also tried: (with same error)
append_view_path(File.join(Rails.root, "app/views/#{current_organization.slug}"))
custom themes depending on the organization
Surely it would make more sense to define custom layouts & CSS rather than entirely different view sets for each company?
--
I would personally do this:
#app/layouts/application.html.erb
<%= stylesheet_link_tag "application", controller_name ... %>
This will give me the ability to style the different pages as per the theme. Obviously a constriction on what you want, but I hope it shows how you could modularize your controllers etc
--
If you wanted to create different "themes" (IE having a completely different view structure per tenant), you'll want to use the prepend_view_path helper, as described here:
#app/controllers/application_controller.rb
Class ApplicationController < ActionController::Base
prepend_view_path("views/#{current_organization.slug}")
end
Try to remove editions in prepend_view_path
prepend_view_path "app/views/#{current_organization.slug}"
Make sure what the way was added. If it doesn't add before_filter

Where is the best place to store layout logic in Rails?

In my Rails app, I have several controllers that makeup a site for my client. With end users and Admins using virtually all of the controllers.
I have created a partial for including navigation links. My problem is that I don't want them to appear inside of the admin section. The part that makes this tricky is that while the navigation should appear for things like pages/home, /galleries or /galleries/5 they should not appear for things like /pages/new or /galleries/5/edit.
Up until now I have been adding logic to application.html.erb, but things are beginning to get out of hand, there are so many rules in my if statement that I feel dirty about it. Here is an example:
snippet from application.html.erb
<% unless current_page?('/') or current_page?('/admin') or current_page?({action: :new}) %>
<%= render "layouts/top_links" %>
<% end %>
This is all I have written so far, but it will almost certainly get even longer if I continue this way. So here are my questions:
Where should I put the logic for this and how should I call it?
Is current_page? the best way to lay this out?
You can put such dirty logic into helper to make it cleaner
# layouts/application.html.erb
nav_links
# application_helper
def nav_links
return "" if action_name.in?['new', 'edit'] || controller_path == 'admin'
render partial: 'layouts/nav'
end
Done.
You could advance with a role based approach, as it is the most widely used method for access control in rails. Indeed, all those if-else loops in application.html.erb would be a little bit of code smell anyway.
Check Ryan Bates CanCan gem for this purpose.
One possibility is to make use of before_filter, which you can set up in your ApplicationController. What you can do
Apply your own flavour to this, but a goofy example might be:
class ApplicationController < ActionController::Base
before_filter :set_navigation_visibility
def set_navigation_visibility
# Your custom logic
end
That method can also be overridden in the subcontrollers if necessary, or modified using the :only and :except options.
Another possibility is making use of a role-based access-control system. There are many, one such library I have used is Simple-Navigation. A google search for role-based access control for rails seems to yield a number of results that might be helpful.

Ruby on Rails - How to manage layouts in admin interface?

I have a very simple site setup using awesome_nested_set and a single table called Pages.
I would like the ability to select different layouts in the admin when creating and updating Pages. What I envisioned is a drop down on the Pages form that allowed me to select a layout/template.
The only thing I know about layouts is you are required to add them to /views/layouts/ and specify the layout at the top of the controller. I need a way to manage layouts on a per Page basis inside the app itself.
Is that even possible? If so, can you explain on a high level how that might be done so I can have a starting point?
edit
Something like this:
You can easily change the layout at render by supplying the :layout key like so:
def some_action
#... stuff
render "some_action", :layout => "custom_layout"
end
You can also set layout to a symbol in the controller definition, and the controller will run the associated method to decide what layout to choose
class UsersController < ApplicationController
layout :decide_layout
private
def decide_layout
some_boolean ? "layout1" : "layout2"
end
end
You can also replace the symbol with a proc if you don't want the method located away from the usage. Finally, you can also call #layout in an action itself.
Assuming you have files in views/layouts called something like one_column.html.erb, two_column.html.erb, etc., and an attribute called layout on you page model, you could just do:
def show
#page = Page.find(params[:id])
render :action => "show", :layout => #page.layout
end
Is that what you're looking for?

Asp.net MasterPage equivalent in Ruby on Rails, Trying to define a site wide layout

Asp.net WebForms and MVC has a concept of Masterpages which make it easy to define a one time layout for all the page of your site. In Rails I'm struggling to find an equivalent usage pattern or feature.
From what I've read it's really easy to define a layout in every action with:
layout: 'viewname'
Now that seemed pretty ceremonial to include in every controller so I added:
layout: 'application'
in the base ApplicationController.
So far this is working ok unless I have a more specific layout in the view pages. Is this common technique for getting a consistent style in your Rails application?
Imagine a simplified blog where we have a controller called PostsController which has two actions: index and show
The index action is called when the user hits http://yourwebsite.com/posts - this action displays all of the available blog posts.
The show action is called when a user gets a specific blog article - i.e. http://yourwebsite.com/posts/article-about-something-interesting
Let's say that we want the index page to have a two column layout and we want the show page for each blog-article to have a three column layout. To achieve this, we would simply define two separate layouts (in app/views/layouts folder) - we'll call the two column layout "application" and we'll call the three-column layout "alternate".
In order to get the index page to use the two-column layout and the show page to use the three-column layout, we could just do the following in our controller:
class PostsController < ApplicationController
def index
#posts = Post.all
render :layout => "application"
end
def show
#post = Post.find(params[:id])
render :layout => "alternate"
end
end
If we want all actions to use the same layout, we can just do this:
class PostsController < ApplicationController
layout "application"
def index
#posts = Post.all
end
def show
#post = Post.find(params[:id])
end
end
Finally, if we do not specify which layout we want to use, then Rails will by default, render any layout which has the same name as the resource we are displaying. So in our example, where our resources are called "Posts", if we define a third layout called posts.html.erb (in app/views/layouts) then Rails will automatically use that layout when the user renders any of the actions in the PostsController - providing of course that we have not explicitly asked Rails to render another layout....
Hope it helps,
This PDF book excerpt from Rails for .Net Developers has a pretty good explanation of Rails layouts, along with a comparison to ASP.Net MasterPages. Since it seems to work pretty well, it's probably used fairly often, at least by developers familiar with the master page concept.

Rails Sub-controllers?

I'm pretty new to Rails and have an issue which I can't quite get my
head around as to the architecturally 'correct' way of doing it.
Problem relates to what I kinda call sub-controllers. The scenario is
this:
I have a series of pages, on which is a panel of some form containing
some information (think the user panel on gitHub top right).
So, in my app, I have controllers that generate the data for the pages
and render out the responses which is fine, but when it comes to this
panel, it seems to me that you would want some sort of controller action
dedicated to generating this panel and it's view.
Question is, how do you go about doing this? How do I render a 'sub
controller' from within a view?
I would put the logic in a helper or a module. (http://api.rubyonrails.org/classes/ActionController/Helpers/ClassMethods.html)
Then render partials where you want these things displayed. (http://api.rubyonrails.org/classes/ActionView/Partials.html)
Like Herman said, if it's logic that you need generated after the controller hands off to the view (ie, the Pages controller generates a page view, but you want a customized panel) then put it in a helper. Or, call a separate method in your Pages controller before handing off to the view. Or, if it's a lot of logic, create a Module and stick it in your /lib folder. So you could have a whole Panel module with methods that generate different parts of your Panel and which are called by your controller. But if you want to call these methods from within the view, then you should use a helper instead.
I dont think a module is what is required here, modules are required for shared behaviour across a small subset of your classes.
What I think is required here is the understanding of the inheritance of ApplicationController and also layouts
so, for example, my layout might look like:
<html>
<head><title>Foo</title></head>
<body>
<%= render :partial => (current_user ? "/shared/user_widget_bar" : "/shared/login_bar") %>
<%= yield %>
</body>
</html>
Any code that i want to use for it would go in my ApplicationController since it would be shared across the majority of my app:
before_filter :generate_user_widget
def generate_user_widget
if current_user
#avatar = ...
#unread_messages = ...
end
end
I understand that it might be cleaner for it to belong in a separate controller BUT honestly, unless the code is huge, it doesn't matter and can even still be put inside a module which is then included by ActionController. However it does need to be inside ApplicationController if you consider the scope of it.
If there are more related pages, say for example, you have a Rails app that manages multiple sites and you want shared behaviour across a particular site, try creating a parent controller which has no actions and only private methods, any controllers that need to have access to those methods can inherit off it. That way you can apply before filters to all controllers which inherit off it, saving you the pain of forgetting to add one in your non-parent controllers.
e.g:
class SiteA::SiteAParentController < ApplicationController
before_filter :generate_user_widget
...
end
class SiteA::ProductController < SiteA::SiteAParentController
def index
...
end
end
well, if you really need to call a controller action from the view, you can use components. They were part of the framework, now they only exist as plugins. One such plugin that seems to be well maintained is here: http://github.com/cainlevy/components/tree/master
from its docs:
== Usage
Note that these examples are very simplistic and would be better implemented using Rails partials.
=== Generator
Running script/generator users details will create a UsersComponent with a "details" view. You might then flesh out
the templates like this:
class UsersComponent < Components::Base
def details(user_or_id)
#user = user_or_id.is_a?(User) ? user_or_id : User.find(user_or_id)
render
end
end
=== From ActionController
class UsersController < ApplicationController
def show
return :text => component("users/detail", params[:id])
end
end
=== From ActionView
<%= component "users/detail", #user %>

Resources