Benjamin Gorman
Benjamin Gorman

Reputation: 370

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:

<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. 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:

def getpages
    @pages = Page.all
end

My first instinct was to replace <% @pages = Page.all %> with <% PagesController.getpages %>, which was unsuccessful. I am confident this problem has something to do with routing, could anyone enlighten me.

Upvotes: 1

Views: 2058

Answers (5)

Pedro Nascimento
Pedro Nascimento

Reputation: 13926

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.

Considering your method is named get_pages in the controller, just write:

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.

Upvotes: 1

Sherwyn Goh
Sherwyn Goh

Reputation: 1352

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. Call it directly as @pages.

Upvotes: 0

nathanvda
nathanvda

Reputation: 50057

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.

But there are definitely better approaches.

I would fix this as follows: in ApplicationController I would add the method

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.

E.g.

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.

The method we called sets the instance variable @pages, it will be available in the view, so your partial would like:

<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. So if you want it for all views, define this in ApplicationController. E.g. this is what we generally do when defining current_user method.

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. This makes your ApplicationController more readable.

    • Note: this is exactly what concerns were introduced for, so you can use that, if you are on rails 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. 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. There is a very good example of rendering a shopping-cart on the page I linked, so you could always check that out.

Upvotes: 1

Richard Peck
Richard Peck

Reputation: 76774

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

Upvotes: 1

alediaferia
alediaferia

Reputation: 2617

Another approach would be using an asynchronous call to load the result of PagesController#index and show it inside your application layout.

$.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? returns true.

Upvotes: 0

Related Questions