Rendering polymorphic object in Rails - ruby-on-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 %>

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.

Which controller should I put #notifications in?

Imagine a typical, modern web app - where in the navbar or some navigation element that runs along the top there is a notifications menu where it tells the user how many notifications they have received since last visiting.
It also may have a dropdown menu that they can clear off existing notifications or just view them.
Given that these notifications need to be present across all views, where should I put the logic for that? In my ApplicationController? That feels wrong, for some reason - but I can't see any other explanation.
Also, should I put the actual partials within the generic /shared/ folder?
For those apps that have a Dashboard controller (i.e. a non-restful resource) that just acts as the central hub for a lot of this info, what's the best way to approach this? In terms of structuring partials that will be included in the Dashboard.
Edit 1
Building off of the answers given by both Kevin and Collin below, I need to go a bit further.
They recommend the Facade pattern as explained by Sandi Metz via Thoughtbot here.
However, this is my issue. In my application.html.erb, I have a partial being rendered - <%= render partial: "shared/navbar" %>. In that partial, I want to be able to call somethings (e.g. that #notifications). How would I access that instance variable and other shared variables/resources on other partials that would be declared in this facades/dashboard.rb?
Use a Facade pattern as described in this article.
See the section on: Only instantiate one object in the controller.
They even use notifications on a dashboard in their example. :)
It's important to remember a couple of things here:
First, only put this logic in your ApplicationController if it's the only place it could possibly go. Since this notifications menu is more than likely persisted across the entire application, that's probably where it belongs.
Secondly, you can implement this Facade while still keeping your DashboardsController quite RESTful. Controllers should only be responsible for instantiating a single object, right? Thus, it makes sense to do something like this:
app/controllers/dashboards_controller.rb
class DashboardsController < ApplicationController
def show
#dashboard = Dashboard.new(current_user)
end
end
app/facades/dashboard.rb
class Dashboard
def initialize(user)
#user = user
end
def new_status
#new_status ||= Status.new
end
def statuses
Status.for(user)
end
def notifications
#notifications ||= user.notifications
end
private
attr_reader :user
end
app/views/dashboards/show.html.erb
<%= render 'profile' %>
<%= render 'groups', groups: #dashboard.group %>
<%= render 'statuses/form', status: #dashboard.new_status %>
<%= render 'statuses', statuses: #dashboard.statuses %>
The Dashboard model gives you back all the pertinent information you need, and your controller gets to focus on a single entity. Beautiful!
UPDATE
In order to use your #dashboard instance variable inside of any partials, it's recommended you render it with a local, which is essentially a designated variable for use inside of a partial.
Here's a great link on how to get started passing locals to a partial.

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.

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

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