如何在Ruby on Rails中实现特定于部分的导航?

时间:2020-03-06 14:54:03  来源:igfitidea点击:

我有一个具有两个或者三个主要"部分"的Ruby / Rails应用程序。当用户访问该部分时,我希望显示一些子导航。这三个部分都使用相同的布局,因此我无法"硬编码"导航到布局中。

我可以想到几种不同的方法来执行此操作。我想为了帮助人们投票,我将其作为答案。

还有其他想法吗?还是我们投票赞成什么?

解决方案

  • 部分渲染。这与helper方法非常相似,除了布局可能包含一些if语句,或者将其传递给helper ...之外。

我们可以在http://rpheath.com/posts/309-rails-plugin-navigation-helper使用类似导航插件的工具

它并没有开箱即用地进行小节导航,但是只要稍作调整,我们就可以将其设置为执行类似的操作。

我建议我们使用局部。有几种方法可以解决此问题。当我创建需要某些特定变量的分部时,我也会为其创建一个辅助方法。

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

现在,partial有一个名为menu_items的局部变量,我们可以在该变量上迭代以创建子菜单。请注意,我建议使用嵌套数组而不是哈希,因为哈希的顺序是不可预测的。

注意,如果我们认为更合理的话,决定应在菜单中显示哪些项目的逻辑也可以在render_submenu内部。

至于子菜单的内容,我们可以在每个控制器中以声明的方式进行处理。

class PostsController < ApplicationController
#...
protected
  helper_method :menu_items
  def menu_items
    [
      ['Submenu 1', url_for(me)],
      ['Submenu 2', url_for(you)]
    ]
  end
end

现在,每当从视图中调用menu_items时,我们都将具有正确的列表以针对特定控制器进行迭代。

与将这种逻辑放在视图模板中相比,这给我提供了一种更干净的解决方案。

请注意,我们可能还需要在ApplicationController中声明一个默认的(空的)menu_items。

警告:高级技巧!

全部渲染。使用CSS / Javascript隐藏不需要的内容,可以使用多种方法对其进行初始化。 (JavaScript可以读取所用的URL,查询参数,cookie中的内容等)。这具有潜在的优势,可以更好地利用缓存发挥作用(为什么要缓存三个视图,然后在可以缓存一个视图时必须同时使它们全部过期) ?),并可以用来提供更好的用户体验。

例如,假设我们有一个带有子导航的通用标签栏界面。如果呈现所有三个标签的内容(即以HTML编写),并且隐藏其中两个标签,则在两个标签之间切换是很简单的Javascript,甚至不会对服务器产生影响。大赢了!用户没有等待时间。没有服务器负载。

想要另一个大胜利?我们可以使用此技术的一种变体来欺骗页面,这些页面在用户之间可能有99%的共同点,但仍然包含用户状态。例如,我们可能拥有一个站点的首页,该站点在所有用户之间相对通用,但是在他们登录时说" Hiya Bob"。将非公共部分(" Hiya,Bob")放入Cookie中。可以通过读取cookie的Javascript来读取页面的该部分。无论页面缓存中的登录状态如何,都为所有用户缓存整个页面。从字面上看,这能够从某些站点的整个Rails堆栈中切出70%的访问权限。

当站点确实是Nginx提供静态资产并带有新HTML页面的Nginx服务偶尔会由在每千次访问中运行的某些Ruby交付时,谁会关心Rails是否可以扩展?)

我本人也问了几乎相同的问题:需要建议:子菜单的Rails视图结构?最好的解决方案可能是使用局部函数。

还有另一种可能的方法:嵌套布局

我不记得我在哪里找到此代码,对原作者感到抱歉。

在lib文件夹中创建一个名为nested_layouts.rb的文件,并包含以下代码:

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

然后,在layouts文件夹中创建各种布局(例如'admin.rhtml'和'application.rhtml')。

现在,在控制器中将其添加到类中:

include NestedLayouts

最后,在操作结束时执行此操作:

def show
  ...
  render :layout => ['admin','application']
end

数组中布局的顺序很重要。无论" yeild"在哪里,管理员布局都将呈现在应用程序布局内。

根据网站的设计以及各种元素的组织方式,此方法确实可以很好地工作。例如,其中一个包含的布局可能只包含一系列div,这些div包含需要为特定操作显示的内容,而较高布局上的CSS可以控制它们的放置位置。

我们可以使用局部函数轻松地做到这一点,假设每个部分都有其自己的控制器。

假设我们有三个部分,分别称为Posts,Users和Admin,每个部分都有自己的控制器:PostsController,UsersController和AdminController。

在每个对应的" views"目录中,声明一个" _subnav.html.erb"局部变量:

/app/views/users/_subnav.html.erb
/app/views/posts/_subnav.html.erb
/app/views/admin/_subnav.html.erb

在每个这些subnav局部变量中,我们都声明了特定于该部分的选项,因此/ users / _subnav.html.erb可能包含:

<ul id="subnav">
  <li><%= link_to 'All Users', users_path %></li>
  <li><%= link_to 'New User', new_user_path %></li>
</ul>

虽然/ posts / _subnav.html.erb可能包含:

<ul id="subnav">
  <li><%= link_to 'All Posts', posts_path %></li>
  <li><%= link_to 'New Post', new_post_path %></li>
</ul>

最后,完成此操作后,只需在布局中包括subnav部分:

<div id="header">...</div>    
<%= render :partial => "subnav" %>
<div id="content"><%= yield %></div>
<div id="footer">...</div>

解决此问题的方法很少。

我们可能想为每个部分使用不同的布局。

我们可能要使用给定目录中所有视图所包含的局部视图。

我们可能希望使用由一个视图或者部分视图填充的" content_for",并在全局布局中调用(如果有的话)。

我个人认为在这种情况下应该避免更多的抽象。