简体   繁体   English

从Rails中的控制器调用方法?

[英]Calling a method from a controller in Rails?

I am a rails beginner and this simple question is really perplexing me. 我是铁轨初学者,这个简单的问题让我很困惑。 On every page, I want a list of all available pages in my app. 在每个页面上,我都想要一个包含我应用中所有可用页面的列表。 So in layouts/application.html.erb I have the following code: 所以在layouts / application.html.erb中我有以下代码:

<ul>
<!-- This loop prints out links to all the pages -->
<% @pages = Page.all %>
<% @pages.each do |page| %>
<li><%= link_to page.name, page_path(page) %></li>
<% end %>

This code DOES work, however I am not satisfied with it because it goes against MVC principles. 这段代码有效,但我对它不满意,因为它违背了MVC原则。 Really instead of querying the database with <% @pages = Page.all %>, I instead want to call the getpages method in the PagesController, which looks like this: 真的不是用<%@pages = Page.all%>查询数据库,而是想在PagesController中调用getpages方法,如下所示:

def getpages
    @pages = Page.all
end

My first instinct was to replace <% @pages = Page.all %> with <% PagesController.getpages %>, which was unsuccessful. 我的第一直觉是用<%PagesController.getpages%>替换<%@pages = Page.all%>,这是不成功的。 I am confident this problem has something to do with routing, could anyone enlighten me. 我相信这个问题与路由有关,任何人都可以启发我。

There are several ways you accomplish this. 有几种方法可以实现这一目标。 I'll try to sum them up, but the easiest one is by using a before_filter on your controller. 我会尝试总结它们,但最简单的方法是在控制器上使用before_filter

Considering your method is named get_pages in the controller, just write: 考虑到你的方法在控制器中被命名为get_pages ,只需写:

class SomeController < ApplicationController
  before_filter :get_pages

  def get_pages
    @pages = Page.all
  end
end

Then, on your view, you'll be able to access it as @pages on all your controller actions. 然后,在您的视图中,您将能够在所有控制器操作上以@pages访问它。

You could use a helper method like this: 您可以使用这样的辅助方法

#app/helpers/application_helper.rb
Class ApplicationHelper
   def pages
       Page.all
   end
end

Helpers are available in your views, so you'll be able to do this: 您可以在视图中找到助手,这样您就可以执行此操作:

#app/views/application/index.html.erb
<% pages.each do |page| %>
   <%= page.title %>
<% end %>

Firstly, let me say that your approach is not that wrong imho. 首先,让我说,你的做法是不是错恕我直言。 Introducing an extra method which is just an alias for Page.all is not really needed, or not really an improvement. 引入一个额外的方法,它只是Page.all的别名, Page.all并不需要,或者不是真正的改进。

But there are definitely better approaches. 但肯定有更好的方法。

I would fix this as follows: in ApplicationController I would add the method 我会修复如下:在ApplicationController我会添加方法

def get_all_pages
  @pages ||= get_all_pages
end

This will make sure that any controller knows that method. 这将确保任何控制器都知道该方法。

Then, if you are sure, you need it on every page, you can just a before_filter in your ApplicationController , but generally, I prefer to write it in the controller itself. 然后,如果你确定,你需要在每个页面上,你可以只在ApplicationController使用before_filter ,但一般来说,我更喜欢在控制器本身中编写它。

Eg 例如

class IdeasController < ApplicationController

  before_action :get_all_pages

  ... the rest of your controller ... 

end

Also, I would extract your list of pages into a partial, store in under app/views/shared/_pages_list.html.erb or app/views/pages/_pages_list.html.erb and then in your application.html.erb just call to render the partial instead. 另外,我会将您的页面列表提取到部分,存储在app/views/shared/_pages_list.html.erbapp/views/pages/_pages_list.html.erb ,然后在您的application.html.erb调用改为渲染部分。

The method we called sets the instance variable @pages , it will be available in the view, so your partial would like: 我们调用的方法设置实例变量@pages ,它将在视图中可用,因此您的部分需要:

<ul>
  <% @pages.each do |page| %>
    <li><%= link_to page.name, page_path(page) %></li>
  <% end %>
</ul>

Lastly, for completeness, let me answer how you can make a controller-method available in the view. 最后,为了完整性,让我回答一下如何在视图中使用控制器方法。 In a controller write 在控制器写入

def get_pages
  # ..do something
end
helper_method :get_pages

This will make the method get_pages available in the view, but only for a controller that was responsible for loading/rendering that view. 这将使方法get_pages在视图中可用,但仅适用于负责加载/呈现该视图的控制器。 So if you want it for all views, define this in ApplicationController . 因此,如果您想要所有视图,请在ApplicationController定义它。 Eg this is what we generally do when defining current_user method. 例如,这是我们在定义current_user方法时通常所做的。

Further improvements/alternatives 进一步改进/替代

  • if you start to get some more code, extract the code related to the page-list-fetching-and-rendering into a separate module, and include that into your ApplicationController . 如果您开始获得更多代码,请将与页面列表获取和呈现相关的代码提取到单独的模块中,并将其包含在ApplicationController This makes your ApplicationController more readable. 这使您的ApplicationController更具可读性。

    • Note: this is exactly what concerns were introduced for, so you can use that, if you are on rails 4 注:这正是concerns被引入,这样你就可以使用,如果你是在轨道上4
  • a Page is its own resource, so you could always create a separate PagesController and make sure it is responsible for rendering the list, and use ajax to fetch on the pages where it is needed. Page是它自己的资源,所以你总是可以创建一个单独的PagesController并确保它负责呈现列表,并使用ajax来获取需要它的页面。 This is a really nice solution, but also a bit more work (overkill?). 这是一个非常好的解决方案,但也有一些工作(矫枉过正?)。 But very nicely structured. 但结构非常好。

  • This problem, where you have different areas on a page, which each should have their own Controller-Model-View, is exactly what the cells gem gem solves in a very clean way. 这个问题,你在页面上有不同的区域,每个区域应该有自己的Controller-Model-View,这正是gem gem宝石以非常干净的方式解决的问题。 There is a very good example of rendering a shopping-cart on the page I linked, so you could always check that out. 在我链接的页面上有一个非常好的例子来呈现购物车,所以你总是可以检查出来。

If so, simply add the following code into action in the controller 如果是这样,只需在控制器中添加以下代码即可

def youraction
  @pages = Page.all
end

This will make the global variable @pages available in your view for use. 这将使您的视图中的全局变量@pages可用。 Call it directly as @pages. 直接将其称为@pages。

Another approach would be using an asynchronous call to load the result of PagesController#index and show it inside your application layout. 另一种方法是使用异步调用来加载PagesController#index的结果并在应用程序布局中显示它。

$.ajax("<%= pages_path %>").done(function(html) {
    $("#pages-list").html(html);
});

To achieve this you could make PagesController#index render with layout: false if the request.xhr? 为了实现这一点,你可以使用layout: false来制作PagesController#index渲染layout: false如果request.xhr? returns true . 返回true

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM