Personalised views for a specific Rails controller - ruby-on-rails

The goal is to generate views for given class that are personalised to a given user.
Model and Controller logic remain the same, but the layout and record presentation would be subject to user preferences.
Given the unicity of User.id it is readily available to create application_controller logic to use a layout with that id.
layout = #user.layout
The layout would be able to invoke its personalised CSS.
But what of the views? and the routing? Given a class Product how can the
resources :products
invoke a subdirectory of app/views/products to use that of the user's id, say
app/views/products/10
Thus leading to a structure of files
app/views/products/10/show.html.erb
app/views/products/10/edit.html.erb
app/views/products/10/index.html.erb
app/views/products/11/show.html.erb
app/views/products/11/edit.html.erb
app/views/products/11/index.html.erb
app/views/products/12/show.html.erb
app/views/products/12/edit.html.erb
app/views/products/12/index.html.erb

Answering your question you have call render in each method in the controller.
def show
...
render "user/show/#{params[:id]}.html.erb"
end
You have more info here: https://guides.rubyonrails.org/layouts_and_rendering.html#using-render
But, unless they are TOTALLY different it would be better using the same view for every record and keeping a field for each one with options to manipulate the view.
#user.layout = {header: true, footer: false, ...}
And in the view:
<% if #user.layout[:header] %>
<div>....</div>
<% end %>

Related

Show view for different Users types

I am planning an application with a User model + different User subtypes (such as Students, Teachers, ...) with a dedicated model for each.
I am planning to authenticate on Users level directly in rails, with no specific gem.
My problem is about handling the Users Show view file:
should I show the users directly in the Show file of the User
controller (adding a lot of conditions in my view file, depending of
the User subtype) or
should I use the User show controller action as a gateway
redirecting the a controller dedicated to each subtype, ...then having a
more straightforward show view.
Your view should be almost free of that kind of logic. That would break MVC and lower the maintainability of the app. You should at least leave that to the controller, something like:
def show
render proper_view_by_user
end
private
def proper_view_by_user
# Define this user_type on your own
# on devise you'd do something like current_user.class_name
switch user_type
when 'Admin' then
'admins_show' # template found in your views folder
when 'NormalUser' then
'normal_show'
else
'normal_show'
end
end
Unless this a learning project, really consider using devise and not rolling your own auth system. Read this.
One way to do this is to use partials, and then render the partial that has the same name as the user subtype:
<%# users/show.html.erb %>
<%= render partial: user.user_subtype %>
<%# users/_admin.html.erb %>
<h1>Admin</h1>
<%# users/_student.html.erb %>
<h1>Student</h1>
<%# users/_teacher.html.erb %>
<h1>Teacher</h1>
As an aside, if you're dealing with access levels I would highly recommend using the Pundit gem. It will save you a lot of headaches.

Rendering polymorphic object in Rails

For people that only want the question, here it is :
Is there a way to specify the folder to look in when you call render on an object? I don't want to specify the view, only the folder to look in.
And for people that want context :
I am working on an activity stream system (something that looks like google+/facebook).
I have "Activities", which are exactly like google+ feeds (or facebook, or whatever!). So, I have a simple loop that display each activities, which are bound to one of the following object (polymorphic) : User, Group, Comment, Note.
In my view that render an activity (views/activities/_activity.html.erb), I have
<%= render activity.object %>
where activity.object is a reference to the bound object (User, Group, Note, Comment). If it's a user, it goes to views/users/_user.html.erb and renders it. For a group, views/groups/_group.html.erb.
That works just fine. However, I come to the point where the rendering of a group in my activities should not be the same rendering as in the group list page. Is there a way to specify the folder to look in when you call render on an object? So that my :
<%= render activity.object %>
would become :
<%= render activity.object, :folder => 'views/activities/' %>
Note that I don't want to specify which view directly, as I don't want to do a case for each of the possible types of objects (User, Group, Note, Comment) in the activity. I want to to have the same behaviour as of right now, which means if it finds a views/activities/_user.html.erb, it would load any user in the activities with that view instead of the one in the views/users/_user.html.erb.
Thanks
I'm not aware of any folder type option, but when I do this I usually do:
<%= render "activities/#{activity.object.class.name.underscore}" %>
That would give you similar behaviour.
EDIT A good point below by Dominic, if your classes are nested in namespaces, you will have to include the appropriate structure.
i.e.
module Foo
class Bar < ActiveRecord::Base
end
end
# class.name is Foo::Bar, underscored is 'foo/bar'
<%= render "activities/#{activity.object_type.underscore}" %>
# will be in
activities/foo/_bar.html
In current Rails (3.2.9), you can define a to_partial_path method on your model to tell it where to look.
For example, you can do:
class User
def to_partial_path; "user"; end
end
Rails will then look for _user.html.erb relative to the view from which you're calling render.
My own approach, which I extracted in polymorphic_render gem - to add suffixes for special partials, but store that partial in resource folders.
In your case views/users/_user.html.erb will have very common user representation (which probably used in users list rendering), but views/users/_user_activity.html.erb will render special partial for user activity.
And inserting that partials is very simple, just <%= polymorphic_render activity.object, :activity %>

Rails: Create a custom UI for a single specific object

I've run into a problem I'm completely unsure how to approach.
I have an app for sharing architectural photos. Users have_many Photos, and users can create Collections which also have_many Photos.
Now I have one customer who is a big name in the industry who would like to work with me to create a totally customized Collection with a very different look and feel from "regular" collections, but essentially the same functionality underneath. I'd like to accommodate this request, but I really have no idea how to do it.
Given that I already have a functioning Collection model and CollectionsController, plus all the views, I'd like to re-use as much of that as possible. So, for instance, the custom Collection needs to override the user facing :show view, but not the admin :edit view.
How would you approach something like this?
I'm trying to understand the most efficient, DRY method for creating a completely custom UI for a single record in the database. I'd be very appreciative of suggestions, including links to articles / books etc, as I haven't been able to find much in this area.
I would allow the creation of Liquid view templates associated with a User and/or Collection (if you want both - per-user templates with per-collection variations - use a polymorphic association) and of course fall back to your default view (also built with Liquid for consistency and reference) for all cases where no custom template is found.
Edit to add suggested details:
Any custom templates should be stored in the database (I would add a test/preview function so the user entering a custom template has the chance to verify their template before publishing it):
# Table name custom_templates
# id :integer
# templatable_type :string
# templatable_id :integer
# contents :text
class CustomTemplate < ActiveRecord::Base
belongs_to :templatable, :polymorphic => true
end
class User
has_one :custom_template, :as => :templatable
end
class Collection
has_one :custom_template, :as => :templatable
end
In your controller action, look for a custom template:
custom_template = #collection.custom_template
custom_template ||= #user.custom_template
#custom_template = Liquid::Template.parse(custom_template.contents) if custom_template
In your view, either render the custom template or your default template partial:
<% if #custom_template -%>
<%= #custom_template.render(_hash_of_objects_to_pass_to_liquid_template_) %>
<% else -%>
<%= render :partial => 'default' %>
<% end -%>

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 %>

How do I implement Section-specific navigation in Ruby on Rails?

I have a Ruby/Rails app that has two or three main "sections". When a user visits that section, I wish to display some sub-navigation. All three sections use the same layout, so I can't "hard code" the navigation into the layout.
I can think of a few different methods to do this. I guess in order to help people vote I'll put them as answers.
Any other ideas? Or what do you vote for?
You can easily do this using partials, assuming each section has it's own controller.
Let's say you have three sections called Posts, Users and Admin, each with it's own controller: PostsController, UsersController and AdminController.
In each corresponding views directory, you declare a _subnav.html.erb partial:
/app/views/users/_subnav.html.erb
/app/views/posts/_subnav.html.erb
/app/views/admin/_subnav.html.erb
In each of these subnav partials you declare the options specific to that section, so /users/_subnav.html.erb might contain:
<ul id="subnav">
<li><%= link_to 'All Users', users_path %></li>
<li><%= link_to 'New User', new_user_path %></li>
</ul>
Whilst /posts/_subnav.html.erb might contain:
<ul id="subnav">
<li><%= link_to 'All Posts', posts_path %></li>
<li><%= link_to 'New Post', new_post_path %></li>
</ul>
Finally, once you've done this, you just need to include the subnav partial in the layout:
<div id="header">...</div>
<%= render :partial => "subnav" %>
<div id="content"><%= yield %></div>
<div id="footer">...</div>
Partial render. This is very similar to the helper method except perhaps the layout would have some if statements, or pass that off to a helper...
As for the content of your submenus, you can go at it in a declarative manner in each controller.
class PostsController < ApplicationController
#...
protected
helper_method :menu_items
def menu_items
[
['Submenu 1', url_for(me)],
['Submenu 2', url_for(you)]
]
end
end
Now whenever you call menu_items from a view, you'll have the right list to iterate over for the specific controller.
This strikes me as a cleaner solution than putting this logic inside view templates.
Note that you may also want to declare a default (empty?) menu_items inside ApplicationController as well.
Warning: Advanced Tricks ahead!
Render them all. Hide the ones that you don't need using CSS/Javascript, which can be trivially initialized in any number of ways. (Javascript can read the URL used, query parameters, something in a cookie, etc etc.) This has the advantage of potentially playing much better with your cache (why cache three views and then have to expire them all simultaneously when you can cache one?), and can be used to present a better user experience.
For example, let's pretend you have a common tab bar interface with sub navigation. If you render the content of all three tabs (i.e. its written in the HTML) and hide two of them, switching between two tabs is trivial Javascript and doesn't even hit your server. Big win! No latency for the user. No server load for you.
Want another big win? You can use a variation on this technique to cheat on pages which might but 99% common across users but still contain user state. For example, you might have a front page of a site which is relatively common across all users but say "Hiya Bob" when they're logged in. Put the non-common part ("Hiya, Bob") in a cookie. Have that part of the page be read in via Javascript reading the cookie. Cache the entire page for all users regardless of login status in page caching. This is literally capable of slicing 70% of the accesses off from the entire Rails stack on some sites.
Who cares if Rails can scale or not when your site is really Nginx serving static assets with new HTML pages occasionally getting delivered by some Ruby running on every thousandth access or so ;)
You could use something like the navigation plugin at http://rpheath.com/posts/309-rails-plugin-navigation-helper
It doesn't do sub-section navigation out of the box, but with a little tweaking you could probably set it up to do something similar.
I suggest you use partials. There are a few ways you can go about it. When I create partials that are a bit picky in that they need specific variables, I also create a helper method for it.
module RenderHelper
#options: a nested array of menu names and their corresponding url
def render_submenu(menu_items=[[]])
render :partial => 'shared/submenu', :locals => {:menu_items => menu_items}
end
end
Now the partial has a local variable named menu_items over which you can iterate to create your submenu. Note that I suggest a nested array instead of a hash because a hash's order is unpredictable.
Note that the logic deciding what items should be displayed in the menu could also be inside render_submenu if that makes more sense to you.
I asked pretty much the same question myself: Need advice: Structure of Rails views for submenus? The best solution was probably to use partials.
There is another possible way to do this: Nested Layouts
i don't remember where i found this code so apologies to the original author.
create a file called nested_layouts.rb in your lib folder and include the following code:
module NestedLayouts
def render(options = nil, &block)
if options
if options[:layout].is_a?(Array)
layouts = options.delete(:layout)
options[:layout] = layouts.pop
inner_layout = layouts.shift
options[:text] = layouts.inject(render_to_string(options.merge({:layout=>inner_layout}))) do |output,layout|
render_to_string(options.merge({:text => output, :layout => layout}))
end
end
end
super
end
end
then, create your various layouts in the layouts folder, (for example 'admin.rhtml' and 'application.rhtml').
Now in your controllers add this just inside the class:
include NestedLayouts
And finally at the end of your actions do this:
def show
...
render :layout => ['admin','application']
end
the order of the layouts in the array is important. The admin layout will be rendered inside the application layout wherever the 'yeild' is.
this method can work really well depending on the design of the site and how the various elements are organized. for instance one of the included layouts could just contain a series of divs that contain the content that needs to be shown for a particular action, and the CSS on a higher layout could control where they are positioned.
There are few approaches to this problem.
You might want to use different layouts for each section.
You might want to use a partial included by all views in a given directory.
You might want to use content_for that is filled by either a view or a partial, and called in the global layout, if you have one.
Personally I believe that you should avoid more abstraction in this case.

Resources